C#で一意のファイル名を生成する方法


131

ハードドライブに保存するファイルに一意の名前を生成するアルゴリズムを実装しました。私は追加していますDateTime時間、分、秒、ミリ秒が、それでも、それは一度イムアップロード、複数のファイルので、ファイルの重複名を生成します。

2つのファイルが同じにならないようにハードドライブに保存するファイルの一意の名前を生成するための最良の解決策は何ですか?


それは他の要件に依存します。この[古い]質問はあいまいでした。
user2864740

回答:


240

読みやすさが重要でない場合は、GUIDを使用してください。

例えば:

var myUniqueFileName = string.Format(@"{0}.txt", Guid.NewGuid());

または短いです

var myUniqueFileName = $@"{Guid.NewGuid()}.txt";

私のプログラムでは、読み取り可能な名前( "Image1.png"… "Image10.png")を生成するために、たとえば10回試行することがあり、失敗した場合(ファイルが既に存在するため)、GUIDにフォールバックします。

更新:

最近、DateTime.Now.TicksGUIDの代わりにも使用しています:

var myUniqueFileName = string.Format(@"{0}.txt", DateTime.Now.Ticks);

または

var myUniqueFileName = $@"{DateTime.Now.Ticks}.txt";

私にとっての利点は、GUIDと比較して、短くて「見栄えの良い」ファイル名が生成されることです。

場合によっては(たとえば、非常に短時間で多数のランダムな名前を生成する場合など)、これにより一意でない値作成される可能性があることに注意してください。

他のコンピューターにファイルを転送する場合でも、ファイル名が一意であることを確認したい場合は、GUIDを使用してください。


7
TickをGUIDとして使用するのはとても醜いです。また、ファイル名の文字長を減らすTicksのハッシュを取得することもできます。DateTime.Now.Ticks.GetHashCode().ToString("x").ToUpper()
WillMcKill 2017

4
「ティック」は予測可能であり、スレッドセーフではありません(同じ「ティック」が複数のスレッド/プロセスから取得される可能性があるため)。このため、一時ファイル名の生成には適していません。X..1..Nの生成は、ユーザー向けのタスク(エクスプローラーでのコピーなど)には適しているかもしれませんが、サーバーの作業には疑わしいものです。
user2864740

90

使用する

Path.GetTempFileName()

または、新しいGUID()を使用します。

MSDNのPath.GetTempFilename()


ここでは、MSDNのドキュメントへのリンクは次のとおりです。msdn.microsoft.com/en-us/library/...
epotterは

3
ただし、そのGetTempFileName()ようなファイルを削除せずに多数作成すると、例外がスローされる場合があります。
Joey

21
「GetTempFileNameメソッドを使用して、以前の一時ファイルを削除せずに65535を超えるファイルを作成すると、IOExceptionが発生します。」MSDNの記事は述べています。
ÇağdaşTekinの

1
警告:ファイルGetTempFileName作成します。これは、一時パスの場所を選択することも意味します。一方、別のパスで使用できるGetRandomFileName8.3 ファイル名を生成するのに適しています。(GetTempFileNameをFile.Deleteと共に使用して、他の場所でファイル名を使用するだけの恐ろしいコードを見たことがあります。)
user2864740 '24

77
System.IO.Path.GetRandomFileName()

MSDNのPath.GetRandomFileName()


ここでは、MSDNのドキュメントへのリンクは次のとおりです。msdn.microsoft.com/en-us/library/...
epotterは

13
@RyBolt:自分でシードする必要がないので、その方法を使用するために覚えておくべきことはほとんどありません。そして、私は大多数の開発者が安全な暗号化システムを構築する方法についての手がかりを持たないことを期待します。
Joey

54

ファイル名の読みやすさが重要でない場合は、多くの人が示唆しているように、GUIDで十分です。ただし、GUIDファイル名が1000のディレクトリを調べると、ソートが非常に困難であることがわかりました。したがって、私は通常、ファイル名にいくつかのコンテキスト情報を与える静的文字列、タイムスタンプ、およびGUIDの組み合わせを使用します。

例えば:

public string GenerateFileName(string context)
{
    return context + "_" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "_" + Guid.NewGuid().ToString("N");
}

filename1 = GenerateFileName("MeasurementData");
filename2 = GenerateFileName("Image");

このように、ファイル名で並べ替えると、ファイルはコンテキスト文字列で自動的にグループ化され、タイムスタンプで並べ替えられます。

Windowsでのファイル名の制限は255文字であることに注意してください。


1
+1 GUID と組み合わせて役立つ情報を含めるよう提案する。-細かいことに注意する必要がありRight Click > Sort By > Dateます。ファイル名に日付と時刻を含めることは、できる限り冗長です。
Timothy Shields 2013年

1
同じディレクトリに異なるコンテキストの一連のファイルを格納している場合、この時間が役立ちます。もちろん、ファイル名の生成は、特定のニーズに基づいて調整する必要があります。
Mas

する必要がありますGuid.NewGuid().ToString();。括弧がありません。それ以外の場合は+1
Laurent W.

これは非常に滑らかです。タイムスタンプとGUID。+1
JoshYates1980 2015年

私はこのソリューションを好きです+ 1、fileNameに追加する2番目のパラメーター文字列拡張子を追加しました。これにより、コンテキストの概念がさらにサポートされ、必要に応じてダブルクリックでデフォルトのアプリケーションでファイルを簡単に開くことができます
shelbypereira

23

以下は、提供されたオリジナルに基づいて一意の読み取り可能なファイル名を返すアルゴリズムです。元のファイルが存在する場合、存在しないファイルが見つかるまで、ファイル名にインデックスを追加しようとします。既存のファイル名をHashSetに読み込んで衝突をチェックするため、かなり高速(私のマシンでは1秒あたり数百のファイル名)であり、スレッドセーフでもあり、競合状態の影響を受けません。

たとえば、それを渡すとtest.txt、次の順序でファイルを作成しようとします。

test.txt
test (2).txt
test (3).txt

など。最大試行回数を指定することも、デフォルトのままにすることもできます。

ここに完全な例があります:

class Program
{
    static FileStream CreateFileWithUniqueName(string folder, string fileName, 
        int maxAttempts = 1024)
    {
        // get filename base and extension
        var fileBase = Path.GetFileNameWithoutExtension(fileName);
        var ext = Path.GetExtension(fileName);
        // build hash set of filenames for performance
        var files = new HashSet<string>(Directory.GetFiles(folder));

        for (var index = 0; index < maxAttempts; index++)
        {
            // first try with the original filename, else try incrementally adding an index
            var name = (index == 0)
                ? fileName
                : String.Format("{0} ({1}){2}", fileBase, index, ext);

            // check if exists
            var fullPath = Path.Combine(folder, name);
            if(files.Contains(fullPath))
                continue;

            // try to create the file
            try
            {
                return new FileStream(fullPath, FileMode.CreateNew, FileAccess.Write);
            }
            catch (DirectoryNotFoundException) { throw; }
            catch (DriveNotFoundException) { throw; }
            catch (IOException) 
            {
                // Will occur if another thread created a file with this 
                // name since we created the HashSet. Ignore this and just
                // try with the next filename.
            } 
        }

        throw new Exception("Could not create unique filename in " + maxAttempts + " attempts");
    }

    static void Main(string[] args)
    {
        for (var i = 0; i < 500; i++)
        {
            using (var stream = CreateFileWithUniqueName(@"c:\temp\", "test.txt"))
            {
                Console.WriteLine("Created \"" + stream.Name + "\"");
            }
        }

        Console.ReadKey();
    }
}

スレッドセーフstatic readonly 変数でもなければlock
Kiquenet 2017年

メソッド自体は静的なので何も共有しないので、複数のスレッドがこのメソッドに同時に安全に入ることができると思います。おそらくスレッドセーフは正しい言葉ではありません-別のスレッド/プロセスが実行中に競合する名前のファイルを作成した場合、このメソッドは回復して次の使用可能な名前を試します。改善できると思われる場合は、自由に編集してください。
Mike Chamberlain

たぶん、「競合状態に苦しんでいない」のが良い方法です。
マイクチェンバレン

10

私はGetRandomFileNameを使用します

GetRandomFileNameメソッドは、フォルダー名またはファイル名のいずれかとして使用できる暗号的に強力なランダムな文字列を返します。GetTempFileNameとは異なり、GetRandomFileNameはファイルを作成しません。ファイルシステムのセキュリティが最も重要な場合は、GetTempFileNameの代わりにこのメソッドを使用する必要があります。

例:

public static string GenerateFileName(string extension="")
{
    return string.Concat(Path.GetRandomFileName().Replace(".", ""),
        (!string.IsNullOrEmpty(extension)) ? (extension.StartsWith(".") ? extension : string.Concat(".", extension)) : "");
}

GetRandomFileName()メソッドは常にGUID()と同様に毎回一意のファイル名を生成しますか?
Ashish Shukla

1
@AshishShukla実際には私にはわからない。msdnは、「暗号的に強力なランダム文字列」が生成されると言います。今のところ問題ありませんでした。一意性が重要な場合は、追加のチェックを行うことをお勧めします。
Koray

3
  1. 通常のプロセスに従ってタイムスタンプ付きのファイル名を作成します
  2. ファイル名が存在するかどうかを確認します
  3. False-ファイルを保存します
  4. True-ファイルに追加の文字、おそらくカウンターを追加します
  5. ステップ2に進む

10
このアルゴリズムは、同時実行に対して脆弱です
Dias

3

カスタムメソッドを使用せずに、一意のファイル名を自動的に生成できます。ただで以下を使用しStorageFolderクラス またはStorageFileクラス。ここでの鍵は次のとおりです。CreationCollisionOption.GenerateUniqueNameNameCollisionOption.GenerateUniqueName

一意のファイル名で新しいファイルを作成するには:

var myFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("myfile.txt", NameCollisionOption.GenerateUniqueName);

一意のファイル名を持つ場所にファイルをコピーするには:

var myFile2 = await myFile1.CopyAsync(ApplicationData.Current.LocalFolder, myFile1.Name, NameCollisionOption.GenerateUniqueName);

移動先の場所に一意のファイル名を持つファイルを移動するには:

await myFile.MoveAsync(ApplicationData.Current.LocalFolder, myFile.Name, NameCollisionOption.GenerateUniqueName);

宛先の場所で一意のファイル名を使用してファイルの名前変更するには:

await myFile.RenameAsync(myFile.Name, NameCollisionOption.GenerateUniqueName);

2

私は次のコードとその正常な動作を使用しています。これがお役に立てば幸いです。

タイムスタンプを使用して一意のファイル名から始めます-

"context_" + DateTime.Now.ToString( "yyyyMMddHHmmssffff")

C#コード-

public static string CreateUniqueFile(string logFilePath, string logFileName, string fileExt)
    {
        try
        {
            int fileNumber = 1;

            //prefix with . if not already provided
            fileExt = (!fileExt.StartsWith(".")) ? "." + fileExt : fileExt;

            //Generate new name
            while (File.Exists(Path.Combine(logFilePath, logFileName + "-" + fileNumber.ToString() + fileExt)))
                fileNumber++;

            //Create empty file, retry until one is created
            while (!CreateNewLogfile(logFilePath, logFileName + "-" + fileNumber.ToString() + fileExt))
                fileNumber++;

            return logFileName + "-" + fileNumber.ToString() + fileExt;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private static bool CreateNewLogfile(string logFilePath, string logFile)
    {
        try
        {
            FileStream fs = new FileStream(Path.Combine(logFilePath, logFile), FileMode.CreateNew);
            fs.Close();
            return true;
        }
        catch (IOException)   //File exists, can not create new
        {
            return false;
        }
        catch (Exception)     //Exception occured
        {
            throw;
        }
    }

1

ファイル名に日付のタイムスタンプが必要ですか?

ファイル名をGUIDにすることができます。


@downvoter反対票の理由は?ファイル名のGUIDは、この質問に対する一般的な回答のようです。
Larry Hipp

それは繰り返しの答えであり、反対票を投じる十分な評判がありません
Dias

@XMLforDummies私の答えは最初の1つでした。たった今時間を表示しているだけなので、今のようには見えないかもしれません。おそらく正解なので、これは繰り返し答えです。
Larry Hipp、2011年


1

を使用Guid.NewGuid()してGUIDを作成し、それをファイル名(または、必要に応じてタイムスタンプと一緒にファイル名の一部)として使用するのはどうでしょう。


1

ファイル拡張子の前にシーケンス番号を追加することにより、Windowsのようにファイル名を生成する単純な再帰関数を作成しました。

の目的のファイルパスがC:\MyDir\MyFile.txtあり、ファイルがすでに存在する場合、最終的なファイルパスのを返しますC:\MyDir\MyFile_1.txt

これは次のように呼ばれます:

var desiredPath = @"C:\MyDir\MyFile.txt";
var finalPath = UniqueFileName(desiredPath);

private static string UniqueFileName(string path, int count = 0)
{
    if (count == 0)
    {
        if (!File.Exists(path))
        {
            return path;
        }
    }
    else
    {
        var candidatePath = string.Format(
            @"{0}\{1}_{2}{3}",
            Path.GetDirectoryName(path),
            Path.GetFileNameWithoutExtension(path),
            count,
            Path.GetExtension(path));

        if (!File.Exists(candidatePath))
        {
            return candidatePath;
        }
    }

    count++;
    return UniqueFileName(path, count);
}

これはスレッドセーフでもプロセスセーフでもありません。File.Existsチェックとファイルの(後で想定される)作成には競合状態があります。簡単に言うと、ファイルを作成せずに続けて2回呼び出されると、同じ結果が返されます。
user2864740

1

以下のように一意のIDを作成できないのはなぜですか。

DateTime.Now.TicksとGuid.NewGuid()。ToString()を使用して結合し、一意のIDを作成できます。

DateTime.Now.Ticksが追加されると、一意のIDが作成された日時を秒単位で確認できます。

コードをご覧ください。

var ticks = DateTime.Now.Ticks;
var guid = Guid.NewGuid().ToString();
var uniqueSessionId = ticks.ToString() +'-'+ guid; //guid created by combining ticks and guid

var datetime = new DateTime(ticks);//for checking purpose
var datetimenow = DateTime.Now;    //both these date times are different.

一意のIDのティックの一部を取得し、後で参照できるように日付と時刻を確認することもできます。

作成した一意のIDをファイル名に添付するか、アプリケーションまたはWebサイトへのユーザーのログイン/ログアウト用の一意のセッションIDを作成するために使用できます。


GUIDがあるのに「ティック」を気にする必要があるのはなぜですか。
user2864740

どのシナリオでも、uniqueSessionIdがいつ生成されたかを確認する必要がある場合は、正確な時間を取得します。また、その特定のティックは生涯に1回だけ発生します。
Jineesh Uvantavida

当然のことながら、ティックについての仮定は無効です。1)複数のオブザーバーが同じ「ティック」を見ることができ(スレッド/プロセスを考える)、2)同じ「ティック」が同じオブザーバーによって複数回観察される可能性があります(十分に速くクエリされた場合)。
user2864740

しかし、によって使用だけで Guid.NewGuid(その事実を無視して、「暗号的にランダム」ではない場合もあります)、そうでない場合は気にしないほど高い確率で、一意のIDが生成される-これは「ティック」よりはるかに高い保証です。したがって、「ティック」には値/値がありません。ここでは、「セカンダリ」データがファイル名に入力されていると想定しています。
user2864740 2018年

(FWIW:「ユニークな時間」についての前述のアサーションが壊れているコードを修正したところです。)
user2864740

0

日時、時間、分などが必要な場合は、静的変数を使用できます。この変数の値をファイル名に追加します。カウンタは0から開始し、ファイルを作成したときに増分できます。この方法では、ファイル内にも秒があるため、ファイル名は確実に一意になります。


0

私は通常これらの線に沿って何かをします:

  • ステムファイル名で開始します(work.dat1たとえば)
  • CreateNewで作成してみてください
  • それがうまくいけば、あなたはファイルを持っています、そうでなければ...
  • 現在の日付/時刻をファイル名に混ぜます(work.2011-01-15T112357.dat例えば)
  • ファイルを作成してみてください
  • うまくいった場合はファイルを取得し、そうでない場合は...
  • ファイル名に単調なカウンターを混ぜます(work.2011-01-15T112357.0001.datたとえば、(GUIDは嫌いです。順序/予測可能性を好みます。)
  • ファイルを作成してみてください。ファイルが作成されるまで、カウンターをチェックして再試行してください。

これがサンプルクラスです:

static class DirectoryInfoHelpers
{
    public static FileStream CreateFileWithUniqueName( this DirectoryInfo dir , string rootName )
    {
        FileStream fs = dir.TryCreateFile( rootName ) ; // try the simple name first

        // if that didn't work, try mixing in the date/time
        if ( fs == null )
        {
            string date = DateTime.Now.ToString( "yyyy-MM-ddTHHmmss" ) ;
            string stem = Path.GetFileNameWithoutExtension(rootName) ;
            string ext  = Path.GetExtension(rootName) ?? ".dat" ;

            ext = ext.Substring(1);

            string fn = string.Format( "{0}.{1}.{2}" , stem , date , ext ) ;
            fs = dir.TryCreateFile( fn ) ;

            // if mixing in the date/time didn't work, try a sequential search
            if ( fs == null )
            {
                int seq = 0 ;
                do
                {
                    fn = string.Format( "{0}.{1}.{2:0000}.{3}" , stem , date , ++seq , ext ) ;
                    fs = dir.TryCreateFile( fn ) ;
                } while ( fs == null ) ;
            }

        }

        return fs ;
    }

    private static FileStream TryCreateFile(this DirectoryInfo dir , string fileName )
    {
        FileStream fs = null ;
        try
        {
            string fqn = Path.Combine( dir.FullName , fileName ) ;

            fs = new FileStream( fqn , FileMode.CreateNew , FileAccess.ReadWrite , FileShare.None ) ;
        }
        catch ( Exception )
        {
            fs = null ;
        }
        return fs ;
    }

}

アルゴリズムを微調整したい場合があります(たとえば、常にファイル名に可能なすべてのコンポーネントを使用します)。コンテキストに依存する-たとえば、ログファイルを作成していて、存在しないようにローテーションしたい場合は、それらすべてに同じパターンの名前を共有する必要があります。

コードは完全ではありません(たとえば、渡されたデータのチェックはありません)。そして、アルゴリズムは完全ではありません(たとえば、ハードドライブをいっぱいにするか、アクセス許可、実際のI / Oエラー、またはその他のファイルシステムエラーが発生した場合、これは、現状では無限ループでハングします)。


0

GUIDと日月年秒ミリ秒の文字列を連結することになり、このソリューションは私のシナリオでは非常に良いと思います



0

私はこれを行うために特別にクラスを書きました。「基本」部分(デフォルトは分単位の正確なタイムスタンプ)で初期化され、その後、一意の名前を付けるために文字を追加します。したがって、最初に生成されたスタンプが1907101215aの場合、2番目は1907101215b、次に1907101215cなどとなります。

25を超える一意のスタンプが必要な場合は、単項「z」を使用して25をカウントします。したがって、1907101215y、1907101215za、1907101215zb、... 1907101215zy、1907101215zza、1907101215zzbなどになります。これにより、スタンプは常に生成された順序で英数字順にソートされます(スタンプの後の次の文字が文字でない限り)。

それはスレッドセーフではなく、時間を自動的に更新せず、何百ものスタンプが必要な場合はすぐに膨張しますが、私のニーズには十分です。

/// <summary>
/// Class for generating unique stamps (for filenames, etc.)
/// </summary>
/// <remarks>
/// Each time ToString() is called, a unique stamp is generated.
/// Stamps are guaranteed to sort alphanumerically in order of generation.
/// </remarks>
public class StampGenerator
{
  /// <summary>
  /// All the characters which could be the last character in the stamp.
  /// </summary>
  private static readonly char[] _trailingChars =
  {
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
    'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y'
  };

  /// <summary>
  /// How many valid trailing characters there are.
  /// </summary>
  /// <remarks>Should always equal _trailingChars.Length</remarks>
  public const int TRAILING_RANGE = 25;

  /// <summary>
  /// Maximum length of the stamp. Hard-coded for laziness.
  /// </summary>
  public const int MAX_LENGTH_STAMP = 28;

  /// <summary>
  /// Base portion of the stamp. Will be constant between calls.
  /// </summary>
  /// <remarks>
  /// This is intended to uniquely distinguish between instances.
  /// Default behavior is to generate a minute-accurate timestamp.
  /// </remarks>
  public string StampBase { get; }

  /// <summary>
  /// Number of times this instance has been called.
  /// </summary>
  public int CalledTimes { get; private set; }

  /// <summary>
  /// Maximum number of stamps that can be generated with a given base.
  /// </summary>
  public int MaxCalls { get; }

  /// <summary>
  /// Number of stamps remaining for this instance.
  /// </summary>
  public int RemainingCalls { get { return MaxCalls - CalledTimes; } }

  /// <summary>
  /// Instantiate a StampGenerator with a specific base.
  /// </summary>
  /// <param name="stampBase">Base of stamp.</param>
  /// <param name="calledTimes">
  /// Number of times this base has already been used.
  /// </param>
  public StampGenerator(string stampBase, int calledTimes = 0)
  {
    if (stampBase == null)
    {
      throw new ArgumentNullException("stampBase");
    }
    else if (Regex.IsMatch(stampBase, "[^a-zA-Z_0-9 \\-]"))
    {
      throw new ArgumentException("Invalid characters in Stamp Base.",
                                  "stampBase");
    }
    else if (stampBase.Length >= MAX_LENGTH_STAMP - 1)
    {
      throw new ArgumentException(
        string.Format("Stamp Base too long. (Length {0} out of {1})",
                      stampBase.Length, MAX_LENGTH_STAMP - 1), "stampBase");
    }
    else if (calledTimes < 0)
    {
      throw new ArgumentOutOfRangeException(
        "calledTimes", calledTimes, "calledTimes cannot be negative.");
    }
    else
    {
      int maxCalls = TRAILING_RANGE * (MAX_LENGTH_STAMP - stampBase.Length);
      if (calledTimes >= maxCalls)
      {
        throw new ArgumentOutOfRangeException(
          "calledTimes", calledTimes, string.Format(
            "Called Times too large; max for stem of length {0} is {1}.",
            stampBase.Length, maxCalls));
      }
      else
      {
        StampBase = stampBase;
        CalledTimes = calledTimes;
        MaxCalls = maxCalls;
      }
    }
  }

  /// <summary>
  /// Instantiate a StampGenerator with default base string based on time.
  /// </summary>
  public StampGenerator() : this(DateTime.Now.ToString("yMMddHHmm")) { }

  /// <summary>
  /// Generate a unique stamp.
  /// </summary>
  /// <remarks>
  /// Stamp values are orered like this:
  /// a, b, ... x, y, za, zb, ... zx, zy, zza, zzb, ...
  /// </remarks>
  /// <returns>A unique stamp.</returns>
  public override string ToString()
  {
    int zCount = CalledTimes / TRAILING_RANGE;
    int trailing = CalledTimes % TRAILING_RANGE;
    int length = StampBase.Length + zCount + 1;

    if (length > MAX_LENGTH_STAMP)
    {
      throw new InvalidOperationException(
        "Stamp length overflown! Cannot generate new stamps.");
    }
    else
    {
      CalledTimes = CalledTimes + 1;
      var builder = new StringBuilder(StampBase, length);
      builder.Append('z', zCount);
      builder.Append(_trailingChars[trailing]);
      return builder.ToString();
    }
  }
}

-1

DateTime.Now.Ticks安全でGuid.NewGuid()はない、醜い、クリーンでほぼ安全なものが必要な場合(1ミリ秒で1,000,000回呼び出す場合など、100%安全ではありません)、次のことを試してください。

Math.Abs(Guid.NewGuid().GetHashCode())

安全とは、非常に短い期間に数ミリ秒の間に何度も呼び出す場合に一意であることを意味します。


私のソリューションのダウンボーターに問題はありますか?私にお知らせください。
Mehdi Dehghani

このGetHashCodeメソッドは、int32ビットの範囲を持つを返しますが、a GUIDは128ビットの範囲を持つため、一意である可能性が高くなります。GUID値の形式が気に入らない場合は、値を呼び出すだけでToString("N")ダッシュが削除されます。
4thex
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.