删除文件例外,删除一行文本

时间:2014-01-06 10:39:40

标签: c# text replace file-handling

我正在尝试在类中实现一些基本的配置文件读/写/编辑功能。 配置文件存储在文件[...] \ config.txt中,该文件存储在'path'变量中。

我的配置文件的语法如下:

param0 value_of_it
param1 value_of_it
something_else you_get_it

第一个空格的剩余部分是查找参数的名称,该行的其余部分被视为该参数的值。

对于背景来说太多了。 现在,当我尝试使用file.delete删除该文件时,我得到:

  

“System.IO.IOException:无法访问文件,因为它正被另一个进程使用”。

以下是相关代码:

    internal void alter(string param, bool replace = false, string value = null) {
    //here
        string buf;

        System.IO.StreamReader reader = new System.IO.StreamReader(path);

        string bufPath = path.Substring(0, path.Length-4)+"_buf.txt";

        System.IO.StreamWriter writer = new StreamWriter(bufPath);

        while((buf = reader.ReadLine()) != null) {
            if(buf.Substring(0, param.Length) != param)
                writer.WriteLine(buf);
            else if(replace)
                writer.WriteLine(param+" "+value);
        }
        writer.Dispose();
        reader.Dispose();
        writer.Close();
        reader.Close();
        //there



        File.Delete(path);
        File.Move(bufPath, path);
    }

该函数将原始文件逐行复制到缓冲区中,但调用中指定的参数除外。该行被忽略,因此被删除,或者如果呼叫这样说就被替换。 然后删除原始文件,并在那里“重置”_buf版本(重命名)。

这些步骤正确执行(_buf文件被创建并正确填充),直到'File.Delete(path)'抛出异常。

我已经尝试在// here和//之间注释整个部分并且只删除文件,这导致相同的异常,所以问题必须是更基本的。

我尝试使用Sysinternals Process Explorer按照建议的here on ServerFault查找有问题句柄的进程,但找不到任何内容。我只在自己的进程中找到了该文件的三个句柄。 在杀死Windows资源管理器后我也运行了应用程序,因为我读过有时它是罪犯;这也没有帮助。

我还检查了我的整个程序并确认了以下内容:

  1. 这是第一次使用此文件,因此过去遗忘的句柄是不可能的。

  2. 这是一个单线程应用程序(到目前为止),因此也排除了死锁和竞争条件。

  3. 所以这是我的问题:我怎样才能找到锁定文件的内容? 或者:有没有更简单的方法来删除或编辑文件中的一行?

2 个答案:

答案 0 :(得分:1)

如果您的进程中有三个句柄到那个文件,那么您将多次打开该文件,或者在句柄处于打开状态时多次调用该函数,或者您处理的幽灵版本处于闲置状态。您应该仔细检查您是否在代码中的其他位置打开此文件。当我在测试中运行你的代码时,它运行正常(如果我不导致下面与Substring函数相关的bug)。

因为“path”中的文件以读取模式打开,所以其他读者可以毫无问题地打开它。但是,只要其他代码尝试写入操作(包括文件元数据,如File.Delete),您就会看到此错误。

您的代码不是异常安全的,因此当您从该循环中的流中读取时,此异常或类似函数抛出的异常将导致句柄保持打开状态,从而导致稍后调用打开该文件的函数失败,但您现在正在File.Delete遇到异常。为避免异常安全问题,请使用try / finally或using(我将在下面提供一个示例)。

可能发生的一个此类异常是在循环中调用Substring,因为行的长度有可能小于完整参数的长度。这里还有另一个问题,如果你传递的参数没有尾随空格,那么你可能会匹配另一个包含第一个参数作为前缀的参数(即“hello”匹配“hello_world”)。

这是一个稍微修复的代码版本,它是异常安全的并修复了Substring问题。

internal void alter( string param, bool replace = false, string value = null )
{
    //here
    string buf;
    string bufPath = path.Substring( 0, path.Length - Path.GetExtension( path ).Length ) + "_buf.txt";

    using ( System.IO.StreamReader reader = new System.IO.StreamReader( path ) )
    {
        string paramWithSpace = param + " ";

        using ( System.IO.StreamWriter writer = new StreamWriter( bufPath ) )
        {
            while ( ( buf = reader.ReadLine() ) != null )
            {
                if ( !buf.StartsWith( paramWithSpace ) )
                    writer.WriteLine( buf );
                else if ( replace )
                    writer.WriteLine( paramWithSpace + value );
            }
        }
    }
        //there

    File.Delete( path );
    File.Move( bufPath, path );
}

但是,您可能希望考虑将配置完全加载到内存中,在内存中多次更改它,然后批量写回一次。这将为您提供更高性能的读/写配置(通常您必须一次编写多个配置更改),它还将简化您的代码。对于简单的实现,请使用通用的Dictionary类。

答案 1 :(得分:1)

以xml格式存储配置会更方便,因为如果您以后需要这样做,您可以选择存储更复杂的值。此外,您可以依靠XDocument加载和保存文件,就像XDocument.Load(path)doc.Save(path)一样简单。例如,您当前的配置文件将如下所示:xml格式:

<?xml version="1.0" encoding="utf-8"?>
<params>
  <param name="param0">value_of_it</param>
  <param name="param1">value_of_it</param>
  <param name="something_else">you_get_it</param>
</params>

您改变现有param值或将新参数添加到配置文件的功能如下:

internal void alterXml(string param, bool replace = false, string value = null)
{
    //load xml configuration file to XDocument object
    var doc = XDocument.Load(path);
    //search for <param> having attribute "name" = param
    var existingParam = doc.Descendants("param").FirstOrDefault(o => o.Attribute("name").Value == param);
    //if such a param element doesn't exist, add new element
    if (existingParam == null)
    {
        var newParam = new XElement("param");
        newParam.SetAttributeValue("name", param);
        newParam.Value = "" + value;
        doc.Root.Add(newParam);
    }
    //else update element's value
    else if (replace) existingParam.Value = "" + value;
    //save modified object back to xml file
    doc.Save(path);
}