保存多个同名文件
Saving multiple files with same name
我正在开发一个 C# WPF 应用程序,该应用程序执行一些数据处理并以 .csv
格式输出文件。我想要实现的是,每次我的程序 运行s,它都会生成一个 test.csv
输出文件,但是当我再次使用新数据 运行 时......新的结果文件会覆盖旧的。所以我正在尝试做一些事情,以便每个创建的文件名都是唯一的。例如,test.csv
,然后是 test1.csv
,test2.csv
等等。我想出了下面的代码片段,但我认为逻辑不正确,我不知道该怎么做。任何帮助将不胜感激!
filename = "C:\test.csv";
if (File.Exists(filename))
{
int count = 0;
for (int i = 0; i < 10; i++)
{
filename = "C:\test" + count + ".csv";
count++;
}
}
在您的原始代码中,您只进行了一次检查。尝试(注意 while
):
filename = "C:\test.csv";
int count = 0;
while (File.Exists(filename))
{
count++;
filename = "C:\test" + count + ".csv";
}
//save file goes here
如果您愿意,可以用这个 for
循环替换 while
:
for(int count = 0; File.Exists(filename); count++)
filename = "C:\test" + count + ".csv";
更新: 正如 @eFloh 指出的(见评论),当上述代码的两个实例工作时可能会出现问题同时。
这是因为在循环和实际的写入磁盘操作之间有轻微的 window,因此其他进程可能会进入中间并写入同名文件,覆盖文件另一个程序正在创建或导致错误。
一个可能的解决方案是在循环执行时将一个特殊文件写入目录(如果存在此类文件则停止执行)以确保两个 运行 实例不会发生冲突。
本质上,该文件将向程序发出另一个程序正在执行此循环的信号 - 因此另一个程序将等待另一个程序终止。
下面代码的解释: using
块正在尝试 创建一个名为 lock_file
的文件在目录中。
FileMode.OpenOrCreate
可以打开文件(如果已经存在)或创建文件(如果不存在)。
FileAccess.Write
make 可以编写 lock_file
FileShare.Read
使 using 等到罚款被其他进程关闭 - 所以程序会等待需要。
- 最后,
FileOptions.DeleteOnClose
,使 using
块在退出时删除 lock_file
。这非常重要,否则 lock_file
将永远留在那里,使该块永远等待删除该文件。
总结(同样,这篇文章的功劳归于@eFloh):
using(new FileStream("C:\lock_file", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 4096, FileOptions.DeleteOnClose))
{
//loop and saving goes here
}
您可以使用 LINQ 检查直到找到可用的文件名
var pathFormat = "c:\test{0}.csv";
var maxIndex = Enumerable.Range(0, int.MaxValue)
.SkipWhile(x => File.Exists(string.Format(pathFormat, x)))
.First();
var csvPath = string.Format(pathFormat, maxIndex);
Console.WriteLine(csvPath);
或者,您可以应用不同的文件命名约定,例如:[文件名]+时间戳以确保其唯一性,因此不需要重复名称检查:
DateTime _dt = DateTime.Now;
string filename = @"C:\test_" +
_dt.Year.ToString() +
_dt.Month.ToString() +
_dt.Month.ToString() +
_dt.Day.ToString() + "_" +
_dt.Hour.ToString()+
_dt.Minute.ToString()+
_dt.Second.ToString() +".csv";
}
必要时,您可以通过添加 _dt.Millisecond.ToString()
来扩展它。或者,您可以在文件名中添加 GUID 以确保其唯一性,而不是使用时间戳。
希望这可能有所帮助。
因为每当你检查并然后尝试写入时,你可能同时创建了另一个文件,你应该循环尝试打开以逃避竞争条件:
int tryCount = 1;
string pathFormat = "c:\test{0}.csv";
while (tryCount <= MAX_TRYCOUNT)
{
try
{
using (FileStream fs = File.Open(String.Format(CultureInfo.InvariantCulture, pathFormat, tryCount), FileMode.CreateNew))
{
//fs.Write or open StreamWriter etc.
}
}
catch (IOException)
{
/* try next filename */
}
catch (Exception ex)
{
/* other unexpected error, escape. */
throw;
}
tryCount++;
}
if (tryCount == MAX_TRYCOUNT)
{
throw new IOException("No free filename available.");
}
另一种方法是锁定所有同时写入的应用程序(或应用程序实例):
string lockFile = Path.Combine(PATH_OF_EXPORTDIR,"SingleInstanceCanWrite");
using(new FileStream(lockFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 8 * 1024, FileOptions.DeleteOnClose))
{
/* find next free filename, open write and close */
}
但这只会使竞争条件朝着正确检查锁定文件的方向移动,您在这里也需要一个 try-catch。 :)
我正在开发一个 C# WPF 应用程序,该应用程序执行一些数据处理并以 .csv
格式输出文件。我想要实现的是,每次我的程序 运行s,它都会生成一个 test.csv
输出文件,但是当我再次使用新数据 运行 时......新的结果文件会覆盖旧的。所以我正在尝试做一些事情,以便每个创建的文件名都是唯一的。例如,test.csv
,然后是 test1.csv
,test2.csv
等等。我想出了下面的代码片段,但我认为逻辑不正确,我不知道该怎么做。任何帮助将不胜感激!
filename = "C:\test.csv";
if (File.Exists(filename))
{
int count = 0;
for (int i = 0; i < 10; i++)
{
filename = "C:\test" + count + ".csv";
count++;
}
}
在您的原始代码中,您只进行了一次检查。尝试(注意 while
):
filename = "C:\test.csv";
int count = 0;
while (File.Exists(filename))
{
count++;
filename = "C:\test" + count + ".csv";
}
//save file goes here
如果您愿意,可以用这个 for
循环替换 while
:
for(int count = 0; File.Exists(filename); count++)
filename = "C:\test" + count + ".csv";
更新: 正如 @eFloh 指出的(见评论),当上述代码的两个实例工作时可能会出现问题同时。
这是因为在循环和实际的写入磁盘操作之间有轻微的 window,因此其他进程可能会进入中间并写入同名文件,覆盖文件另一个程序正在创建或导致错误。
一个可能的解决方案是在循环执行时将一个特殊文件写入目录(如果存在此类文件则停止执行)以确保两个 运行 实例不会发生冲突。
本质上,该文件将向程序发出另一个程序正在执行此循环的信号 - 因此另一个程序将等待另一个程序终止。
下面代码的解释: using
块正在尝试 创建一个名为 lock_file
的文件在目录中。
FileMode.OpenOrCreate
可以打开文件(如果已经存在)或创建文件(如果不存在)。FileAccess.Write
make 可以编写lock_file
FileShare.Read
使 using 等到罚款被其他进程关闭 - 所以程序会等待需要。- 最后,
FileOptions.DeleteOnClose
,使using
块在退出时删除lock_file
。这非常重要,否则lock_file
将永远留在那里,使该块永远等待删除该文件。
总结(同样,这篇文章的功劳归于@eFloh):
using(new FileStream("C:\lock_file", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 4096, FileOptions.DeleteOnClose))
{
//loop and saving goes here
}
您可以使用 LINQ 检查直到找到可用的文件名
var pathFormat = "c:\test{0}.csv";
var maxIndex = Enumerable.Range(0, int.MaxValue)
.SkipWhile(x => File.Exists(string.Format(pathFormat, x)))
.First();
var csvPath = string.Format(pathFormat, maxIndex);
Console.WriteLine(csvPath);
或者,您可以应用不同的文件命名约定,例如:[文件名]+时间戳以确保其唯一性,因此不需要重复名称检查:
DateTime _dt = DateTime.Now;
string filename = @"C:\test_" +
_dt.Year.ToString() +
_dt.Month.ToString() +
_dt.Month.ToString() +
_dt.Day.ToString() + "_" +
_dt.Hour.ToString()+
_dt.Minute.ToString()+
_dt.Second.ToString() +".csv";
}
必要时,您可以通过添加 _dt.Millisecond.ToString()
来扩展它。或者,您可以在文件名中添加 GUID 以确保其唯一性,而不是使用时间戳。
希望这可能有所帮助。
因为每当你检查并然后尝试写入时,你可能同时创建了另一个文件,你应该循环尝试打开以逃避竞争条件:
int tryCount = 1;
string pathFormat = "c:\test{0}.csv";
while (tryCount <= MAX_TRYCOUNT)
{
try
{
using (FileStream fs = File.Open(String.Format(CultureInfo.InvariantCulture, pathFormat, tryCount), FileMode.CreateNew))
{
//fs.Write or open StreamWriter etc.
}
}
catch (IOException)
{
/* try next filename */
}
catch (Exception ex)
{
/* other unexpected error, escape. */
throw;
}
tryCount++;
}
if (tryCount == MAX_TRYCOUNT)
{
throw new IOException("No free filename available.");
}
另一种方法是锁定所有同时写入的应用程序(或应用程序实例):
string lockFile = Path.Combine(PATH_OF_EXPORTDIR,"SingleInstanceCanWrite");
using(new FileStream(lockFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 8 * 1024, FileOptions.DeleteOnClose))
{
/* find next free filename, open write and close */
}
但这只会使竞争条件朝着正确检查锁定文件的方向移动,您在这里也需要一个 try-catch。 :)