【问题标题】:How can I create a temp file with a specific extension with .NET?如何使用 .NET 创建具有特定扩展名的临时文件?
【发布时间】:2010-10-09 13:20:15
【问题描述】:

我需要生成一个具有 .csv 扩展名的唯一临时文件。

我现在做的是

string filepath = System.IO.Path.GetTempFileName().Replace(".tmp", ".csv");

但是,这并不能保证我的 .csv 文件是唯一的。

我知道我遇到冲突的几率非常低(尤其是如果您认为我没有删除 .tmp 文件),但这段代码对我来说并不好。

当然,我可以手动生成随机文件名,直到最终找到一个唯一的文件名(这应该不是问题),但我很想知道其他人是否找到了解决这个问题的好方法。

【问题讨论】:

  • 关于 GetTempFileName 的一些注意事项 如果 GetTempFileName 方法用于创建超过 65535 个文件而不删除以前的临时文件,它将引发 IOException。如果没有唯一的临时文件名可用,GetTempFileName 方法将引发 IOException。要解决此错误,请删除所有不需要的临时文件。
  • 临时文件主要用于一组特定的条件。如果文件扩展名很重要,我想知道是否使用 GetTempFileName 不是写解决方案。我知道这已经很长时间了,但是如果您告诉我们更多有关这些文件的上下文和需求的信息,我们也许可以提出一个更好的方法。更多:support.microsoft.com/kb/92635?wa=wsignin1.0
  • 记住GetTempFileName() 每次调用它都会创建一个新文件。 - 如果您立即将字符串更改为其他内容,您只是在临时目录中创建了一个新的零字节文件(正如其他人所指出的,当您在其中点击 65535 个文件时,这最终会导致它失败......) - - 为避免这种情况,请确保删除您在该文件夹中创建的所有文件(包括GetTempFileName() 返回的文件,最好在 finally 块中)。

标签: c# .net file temporary-files


【解决方案1】:

保证是(统计上)唯一的:

string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv"; 

(引用关于碰撞概率的 wiki 文章:

...一个人每年被击中的风险 陨石估计是一颗 170 亿的机会 [19],这意味着 概率约为 0.00000000006 (6 × 10−11),相当于 创造数十万亿 一年内的 UUID 并拥有一个 复制。换句话说,只有在 每产生 10 亿个 UUID 在接下来的 100 年里, 只创造一个的概率 重复将是大约 50%。这 一个重复的概率是 如果地球上的每个人,大约 50% 拥有 6 亿个 UUID

编辑:另请参阅 JaredPar 的 cmets。

【讨论】:

  • 但不保证在可写位置
  • 而且它们完全保证是唯一的,只是统计上不太可能。
  • @Pax:你有更多的机会连续赢得 1000 次彩票,而不是生成两个 idebtical guid。我猜这已经够独特了……
  • @Mitch 它不是唯一的原因是因为我可以可能在同一路径中简单地创建一个具有相同名称的文件。 GUID 虽然保证是唯一的,但也是可预测的,这意味着如果有足够的信息,我可以猜出你的盒子生成的下一组 guid
  • 善良的人们,努力让你的头远离云层。方法是:生成一个随机文件名,如果不存在则创建。所以只要帮助他很好地编码。所有这些关于伪随机生成器和普遍唯一数字的讨论都是完全没有必要的。
【解决方案2】:

试试这个功能...

public static string GetTempFilePathWithExtension(string extension) {
  var path = Path.GetTempPath();
  var fileName = Guid.NewGuid().ToString() + extension;
  return Path.Combine(path, fileName);
}

它将返回带有您选择的扩展名的完整路径。

请注意,不能保证生成唯一的文件名,因为从技术上讲,其他人可能已经创建了该文件。然而,有人猜测您的应用程序生成的下一个 guid 并创建它的机会非常低。假设这将是独一无二的,这是非常安全的。

【讨论】:

  • 也许 Path.ChangeExtension() 会比 Guid.NewGuid().ToString() + extension 更优雅
  • @ohadsc - 事实上,Guid.NewGuid().ToString() + extension 甚至都不正确,应该是 Guid.NewGuid().ToString() + "." + extension
  • 我想这取决于方法的约定(是否期望.txttxt),但由于ChangeExtension 处理这两种情况,它不会受到伤害
  • 称它为后缀而不是扩展名,我猜每个人都很高兴
【解决方案3】:
public static string GetTempFileName(string extension)
{
  int attempt = 0;
  while (true)
  {
    string fileName = Path.GetRandomFileName();
    fileName = Path.ChangeExtension(fileName, extension);
    fileName = Path.Combine(Path.GetTempPath(), fileName);

    try
    {
      using (new FileStream(fileName, FileMode.CreateNew)) { }
      return fileName;
    }
    catch (IOException ex)
    {
      if (++attempt == 10)
        throw new IOException("No unique temporary file name is available.", ex);
    }
  }
}

注意:这类似于 Path.GetTempFileName。创建一个空文件以保留文件名。如果 Path.GetRandomFileName(); 产生冲突,它会尝试 10 次;

【讨论】:

  • 请记住,Path.GetRandomFileName() 实际上会在磁盘上创建一个零字节文件并返回该文件的完整路径。您没有使用此文件,仅更改其名称以更改扩展名。因此,如果您不确定要删除这些临时文件,则在调用此函数 65535 次后,它将开始失败。
  • 你混合了GetTempFileName()GetRandomFileName()GetTempFileName() 像我的方法一样创建一个零字节文件,但是GetRandomFileName() 不创建文件。来自文档: > 与 GetTempFileName 不同,GetRandomFileName 不会创建文件。您的链接指向错误的页面。
【解决方案4】:

您也可以使用System.CodeDom.Compiler.TempFileCollection

string tempDirectory = @"c:\\temp";
TempFileCollection coll = new TempFileCollection(tempDirectory, true);
string filename = coll.AddExtension("txt", true);
File.WriteAllText(Path.Combine(tempDirectory,filename),"Hello World");

这里我使用了 txt 扩展名,但你可以指定任何你想要的。我还将keep标志设置为true,以便临时文件在使用后保留。不幸的是,TempFileCollection 会为每个扩展名创建一个随机文件。如果需要更多临时文件,可以创建多个 TempFileCollection 实例。

【讨论】:

    【解决方案5】:

    为什么不检查文件是否存在?

    string fileName;
    do
    {
        fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv";
    } while (System.IO.File.Exists(fileName));
    

    【讨论】:

    • File.Exists 告诉您有关过去的信息,因此不可靠。在 File.Exist 返回和执行代码之间,可以创建文件。
    • ...那么您自己的程序可能是安全的,但其他进程在该完全相同的目标上写入文件则不是...
    • @JaredPar 发生这种情况的可能性是什么?
    • @Migol 非常低,根据定义,这是一种特殊情况。嗯,这正是异常的设计用途。
    • @CodyGray 发生 guid 冲突的几率是 1/2^128。它发生 2 次的机会是 1/2^256 等。不要打扰!
    【解决方案6】:

    C++'s GetTempFileName 的 MSDN 文档讨论并回答了您的问题:

    GetTempFileName 无法保证文件名是唯一的

    仅使用 uUnique 参数的低 16 位。如果 lpPathName 和 lpPrefixString 参数保持不变,这会将 GetTempFileName 限制为最多 65,535 个唯一文件名。

    由于用于生成文件名的算法,GetTempFileName 在创建大量具有相同前缀的文件时性能不佳。在这种情况下,建议您根据 GUID 构造唯一的文件名

    【讨论】:

    • GetTempFileName 方法如果用于创建超过 65535 个文件而不删除以前的临时文件,则会引发 IOException。如果没有唯一的临时文件名可用,GetTempFileName 方法将引发 IOException。要解决此错误,请删除所有不需要的临时文件。
    • 这是一个不完整的报价。相关引用:“如果 uUnique 不为零,您必须自己创建文件。仅创建文件名,因为 GetTempFileName 无法保证文件名是唯一的。”如果你按照大家在这里讨论的方式来称呼它,uUnique 将为零。
    【解决方案7】:

    怎么样:

    Path.Combine(Path.GetTempPath(), DateTime.Now.Ticks.ToString() + "_" + Guid.NewGuid().ToString() + ".csv")
    

    计算机在同一时刻生成相同 Guid 的可能性很小。我在这里看到的唯一弱点是 DateTime.Now.Ticks 将添加的性能影响。

    【讨论】:

      【解决方案8】:

      您还可以执行以下操作

      string filepath = Path.ChangeExtension(Path.GetTempFileName(), ".csv");
      

      这也可以按预期工作

      string filepath = Path.ChangeExtension(Path.GetTempPath() + Guid.NewGuid().ToString(), ".csv");
      

      【讨论】:

      • 如果有文件 temp.csv 并且您创建 temp.tmp 然后将扩展名更改为 csv,这将失败
      • 不,它不会...GetTempFileName() 创建一个唯一的文件名...达到32K 的某个限制,此时您需要删除一些文件,但我认为我的解决方案是正确的。如果我将文件路径传递给不能保证唯一的 ChangeExtension 是错误的,但这不是我的解决方案所做的。
      • GetTempFileName 保证它返回的路径是唯一的。并不是它返回的路径+“.csv”是唯一的。正如大卫所说,以这种方式更改扩展名可能会失败。
      • GetTempFileName 创建一个文件,所以你的第一个例子是资源泄漏。
      • 第一个解决方案应该有一个警告,因为当%TEMP%目录中的文件太多时,它最终可能会导致IOException "The file exists"。问题是GetTempFileName()会创建一个物理空文件并返回其名称。 ChangeExtension() 更改字符串名称,但不更改物理文件的名称,因此原始 .tmp 文件被遗忘/泄漏..
      【解决方案9】:

      在我看来,这里提出的大多数答案都是次优的。最接近的是最初由 Brann 提出的那个。

      临时文件名必须是

      • 独特
      • 无冲突(尚不存在)
      • 原子(在同一操作中创建名称和文件)
      • 很难猜到

      由于这些要求,自己编写这样的野兽并不是一个好主意。编写 IO 库的聪明人会担心锁定(如果需要)等问题。 因此,我认为没有必要重写 System.IO.Path.GetTempFileName()。

      这个,即使它看起来很笨拙,也应该可以完成这项工作:

      //Note that this already *creates* the file
      string filename1 = System.IO.Path.GetTempFileName()
      // Rename and move
      filename = filename.Replace(".tmp", ".csv");
      File.Move(filename1 , filename);
      

      【讨论】:

      【解决方案10】:

      我混合了 @Maxence@Mitch Wheat 的答案,记住我想要 GetTempFileName 方法的语义(fileName 是创建的新文件的名称)添加首选扩展名。

      string GetNewTempFile(string extension)
      {
          if (!extension.StartWith(".")) extension="." + extension;
          string fileName;
          bool bCollisions = false;
          do {
              fileName = Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString() + extension);
              try
              {
                  using (new FileStream(fileName, FileMode.CreateNew)) { }
                  bCollisions = false;
              }
              catch (IOException)
              {
                  bCollisions = true;
              }
          }
          while (bCollisions);
          return fileName;
      }
      

      【讨论】:

        【解决方案11】:

        这对你来说可能很方便...这是创建一个临时文件。文件夹并在 VB.NET 中将其作为字符串返回。

        轻松转换为 C#:

        Public Function GetTempDirectory() As String
            Dim mpath As String
            Do
                mpath = System.IO.Path.Combine(System.IO.Path.GetTempPath, System.IO.Path.GetRandomFileName)
            Loop While System.IO.Directory.Exists(mpath) Or System.IO.File.Exists(mpath)
            System.IO.Directory.CreateDirectory(mpath)
            Return mpath
        End Function
        

        【讨论】:

          【解决方案12】:

          这对我来说似乎工作正常:它检查文件是否存在并创建文件以确保它是可写位置。 应该可以正常工作,您可以将其更改为直接返回 FileStream(这通常是您需要的临时文件):

          private string GetTempFile(string fileExtension)
          {
            string temp = System.IO.Path.GetTempPath();
            string res = string.Empty;
            while (true) {
              res = string.Format("{0}.{1}", Guid.NewGuid().ToString(), fileExtension);
              res = System.IO.Path.Combine(temp, res);
              if (!System.IO.File.Exists(res)) {
                try {
                  System.IO.FileStream s = System.IO.File.Create(res);
                  s.Close();
                  break;
                }
                catch (Exception) {
          
                }
              }
            }
            return res;
          } // GetTempFile
          

          【讨论】:

            【解决方案13】:

            C# 中的简单函数:

            public static string GetTempFileName(string extension = "csv")
            {
                return Path.ChangeExtension(Path.GetTempFileName(), extension);
            }
            

            【讨论】:

            • GetTempFileName() 在临时文件夹中创建一个临时文件。结果,您将创建两个临时文件,其中一个会泄漏。
            • @evgenybf ,我们没有创建两个文件。我们正在生成一个文件,然后更改扩展名。
            • 你错了。查看文档:docs.microsoft.com/en-us/dotnet/api/…GetTempFileName() 创建一个文件并返回其名称。然后,您取其名称并更改其扩展名。但它只影响字符串名称,而不影响磁盘上物理文件的名称。然后您返回修改后的名称,忘记磁盘上的临时文件。
            • 多次调用该方法并查看您的 %TEMP% 目录。那里会有很多空的 .tmp 文件。一段时间后,当 tmp 文件过多(>~60k)时,GetTempFileName() 将开始抛出 IOException "The file exists"。
            【解决方案14】:

            根据我从网上找到的答案,我的代码如下:

            public static string GetTemporaryFileName()
            {       
                string tempFilePath = Path.Combine(Path.GetTempPath(), "SnapshotTemp");
                Directory.Delete(tempFilePath, true);
                Directory.CreateDirectory(tempFilePath);
                return Path.Combine(tempFilePath, DateTime.Now.ToString("MMddHHmm") + "-" + Guid.NewGuid().ToString() + ".png");
            }
            

            正如 Jay Hilyard 编写的 C# Cookbook 一样,Stephen Teilhet 指出 Using a Temporary File in Your Application

            • 您应该在需要存储时使用临时文件 信息暂时供以后检索。

            • 您必须记住的一件事是删除此临时文件 在创建它的应用程序终止之前。

            • 如果没有被删除,它会保留在用户的临时 目录,直到用户手动删除它。

            【讨论】:

              【解决方案15】:

              这就是我正在做的:

              字符串 tStamp = String.Format("{0:yyyyMMdd.HHmmss}", DateTime.Now); 字符串 ProcID = Process.GetCurrentProcess().Id.ToString(); 字符串 tmpFolder = System.IO.Path.GetTempPath(); 字符串 outFile = tmpFolder + ProcID + "_" + tStamp + ".txt";

              【讨论】:

              • 好:包括进程标识符 坏:不包括线程标识符(尽管您可以在锁内运行它) 坏:时间戳只有 1 秒分辨率。在很多很多应用程序中,每秒生成很多文件是很常见的。
              【解决方案16】:

              这是一种生成增量文件名的简单但有效的方法。它将直接查看当前(您可以轻松地将其指向其他地方)并搜索具有基本 YourApplicationName*.txt 的文件(同样您可以轻松更改它)。它将从 0000 开始,因此第一个文件名将是 YourApplicationName0000.txt。如果由于某种原因在左右部分(不是数字)之间存在带有垃圾的文件名,则这些文件将通过 tryparse 调用而被忽略。

                  public static string CreateNewOutPutFile()
                  {
                      const string RemoveLeft = "YourApplicationName";
                      const string RemoveRight = ".txt";
                      const string searchString = RemoveLeft + "*" + RemoveRight;
                      const string numberSpecifier = "0000";
              
                      int maxTempNdx = -1;
              
                      string fileName;
                      string [] Files = Directory.GetFiles(Directory.GetCurrentDirectory(), searchString);
                      foreach( string file in Files)
                      {
                          fileName = Path.GetFileName(file);
                          string stripped = fileName.Remove(fileName.Length - RemoveRight.Length, RemoveRight.Length).Remove(0, RemoveLeft.Length);
                          if( int.TryParse(stripped,out int current) )
                          {
                              if (current > maxTempNdx)
                                  maxTempNdx = current;
                          }
                      }
                      maxTempNdx++;
                      fileName = RemoveLeft + maxTempNdx.ToString(numberSpecifier) + RemoveRight;
                      File.CreateText(fileName); // optional
                      return fileName;
                  }
              

              【讨论】:

                【解决方案17】:

                我认为你应该试试这个:

                string path = Path.GetRandomFileName();
                path = Path.Combine(@"c:\temp", path);
                path = Path.ChangeExtension(path, ".tmp");
                File.Create(path);
                

                它生成一个唯一的文件名并在指定位置创建一个具有该文件名的文件。

                【讨论】:

                • 这个解决方案有很多问题。不能将 C:\temp 与绝对路径组合,c:\temp 可能不可写,并且不能保证 .tmp 版本的文件是唯一的。
                猜你喜欢
                • 2012-02-16
                • 1970-01-01
                • 2016-12-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-11-05
                • 1970-01-01
                相关资源
                最近更新 更多