我正在尝试在类中实现一些基本的配置文件读/写/编辑功能。 配置文件存储在文件[...] \ 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资源管理器后我也运行了应用程序,因为我读过有时它是罪犯;这也没有帮助。
我还检查了我的整个程序并确认了以下内容:
这是第一次使用此文件,因此过去遗忘的句柄是不可能的。
这是一个单线程应用程序(到目前为止),因此也排除了死锁和竞争条件。
所以这是我的问题:我怎样才能找到锁定文件的内容? 或者:有没有更简单的方法来删除或编辑文件中的一行?
答案 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);
}