【问题标题】:Deleting a file exception, deleting a line of text删除文件异常,删除一行文字
【发布时间】:2014-01-23 17:59:38
【问题描述】:

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

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

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 和 //there 之间的整个部分,只删除文件,这会导致相同的异常,因此问题必须具有更基本的性质。

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

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

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

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

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

【问题讨论】:

  • 关于使用“使用”:我已经考虑过这一点,一旦解决,可能会应用它。不过现在:即使我使用“使用”语句;如果我不知道是什么锁定了该文件,我将永远无法做我想做的事,无论我是否遇到异常(到目前为止我的方式)或什么都没做('使用'方式) .
  • 首先是Close,然后是Dispose(不是其他情况)。这可能是一个原因。
  • @Sinatr:可悲的是没有帮助。已经离开了整个读/写部分,只是尝试了File.Delete(path),结果相同:-/
  • 您当前的代码在这里可以正常工作,我刚刚尝试过。也许您有记事本或任何其他程序访问该文件,我不确定。无论如何,您有更好的选择来存储配置文件。您可以使用 xml 格式,请参阅我的答案以了解如何使用..

标签: c# text replace file-handling


【解决方案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>

您更改现有参数值或向配置文件添加新参数的功能将如下所示:

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);
}

【讨论】:

  • 所以这看起来和上面的 Conor Stokes 建议的一样,但是需要更少的手册。我将文件加载到内存中,做任何我想做的事,然后保存它。我也会试一试。但是我对 XML 不是很熟悉,所以我们会看到结果。我可以找到这是什么命名空间?
  • XDocument 在 System.Xml.Linq 命名空间中。你不会后悔尝试这个,拥有 xml 的基本知识可能会在以后的许多其他场景中帮助你:)
  • 我对@9​​87654328@ 的唯一选择是架构、序列化、Xsl 和 XPath 这可能与我仅限于 .NET 3.0 相关吗?另外,我没有使用 VS,而是使用了Sharp-develop
  • System.Xml.Linq 与System.Xml 在不同的程序集中。您需要添加using System.Xml.Linq;
  • 我现在开始使用 XML,你是对的:它更好,值得研究。现在我可以完全延迟多层和层次。再次竖起大拇指! (:
【解决方案2】:

如果您的进程中有该文件的三个句柄,那么您将多次打开该文件,或者在句柄保持打开状态的情况下多次调用该函数,或者您的处理存在幽灵版本。您应该仔细检查您没有在代码的其他地方打开此文件。当我在测试中运行您的代码时,它运行良好(如果我没有导致与下面的 Substring 函数相关的错误)。

因为“路径”处的文件是以读取模式打开的,所以其他阅读器可以毫无问题地打开它。但是,只要其他代码尝试写入操作(包括文件元数据,例如 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 类。

【讨论】:

  • 我对“幽灵句柄”进行了更多调查:根据进程资源管理器,它们都具有相同的 PID,这让我更加困惑。至于在 hello_world 中也可以找到“hello”:实际上,我在过去的版本中已经涵盖了这一点,但在来这里的路上又忘记了它,再加上你对它的看法比我的更优雅,可能也更高效。我现在正试图在记忆中完成这一切。我会将原始文件逐行读入字典,进行操作,然后在完成后将它们写下来。已经谢谢了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-12
  • 1970-01-01
相关资源
最近更新 更多