Как создать уникальные имена файлов в С#

Я реализовал алгоритм, который будет генерировать уникальные имена для файлов, которые будут сохраняться на жестком диске. Я добавляю DateTime: Часы, минуты, секунды и миллисекунды, но при этом он генерирует дублируемое имя файлов, потому что я загружаю несколько файлов одновременно.

Какое наилучшее решение для создания уникальных имен для файлов, которые будут храниться на жестком диске, поэтому нет двух файлов?

Ответ 1

Если читаемость не имеет значения, используйте GUID.

например:.

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

или короче:

var myUniqueFileName = [email protected]"{Guid.NewGuid()}.txt";

В моих программах я иногда пытаюсь, например. 10 раз для создания читаемого имени ( "Image1.png".. "Image10.png" ), и если это не удается (поскольку файл уже существует), я возвращаюсь к идентификаторам GUID.

Update:

Недавно я также использовал DateTime.Now.Ticks вместо GUID:

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

или

var myUniqueFileName = [email protected]"{DateTime.Now.Ticks}.txt";

Преимущество для меня в том, что это генерирует более короткое и "более приятное" имя файла по сравнению с GUID.

Обратите внимание, что в некоторых случаях (например, при генерации большого количества случайных имен за очень короткое время) это может привести к не уникальным значениям.

Придерживайтесь GUID, если вы действительно уверены, что имена файлов уникальны, даже если они переносятся на другие компьютеры.

Ответ 4

Если читаемость имени файла не важна, тогда GUID, как это предлагают многие, будет делать. Тем не менее, я считаю, что поиск в каталоге с 1000 именами GUID файлов очень сложный для сортировки. Поэтому я обычно использую комбинацию статической строки, которая дает имя файла некоторую контекстную информацию, временную метку и GUID.

Например:

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

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

Таким образом, когда я сортирую по имени файла, он автоматически группирует файлы по строке контекста и сортирует по метке времени.

Обратите внимание, что ограничение имени файла в окнах составляет 255 символов.

Ответ 5

Здесь приведен алгоритм, который возвращает уникальное читаемое имя файла на основе оригинала. Если исходный файл существует, он постепенно пытается добавить индекс к имени файла, пока не найдет тот, который не существует. Он считывает существующие имена файлов в HashSet, чтобы проверять наличие конфликтов, поэтому он довольно быстро (несколько сотен имен файлов в секунду на моей машине), он также защищен потоком и не страдает от условий гонки.

Например, если вы передадите его 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();
    }
}

Ответ 6

Я использую 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)) : "");
}

Ответ 7

  • Создайте свое timestamped filename после вашего обычного процесса
  • Проверьте, существует ли имя файла.
  • False - сохранить файл
  • True - добавить дополнительный символ в файл, возможно, счетчик
  • Перейдите к шагу 2

Ответ 8

Я использую следующий код и его работоспособность. Надеюсь, это поможет вам.

Я начинаю с уникального имени файла, используя отметку времени -

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

Код С# -

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

Ответ 9

У вас может быть уникальное имя файла, автоматически созданное для вас без каких-либо настраиваемых методов. Просто используйте следующее с классом StorageFolder или классом StorageFile. Ключ здесь: CreationCollisionOption.GenerateUniqueName и NameCollisionOption.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);

Ответ 10

Вам нужна отметка времени даты в имени файла?

Вы можете сделать имя файла GUID.

Ответ 11

Как использовать Guid.NewGuid() для создания GUID и использовать его как имя файла (или часть имени файла вместе с меткой времени, если хотите).

Ответ 12

Я написал простую рекурсивную функцию, которая генерирует имена файлов, например 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);
}

Ответ 13

Почему мы не можем сделать уникальный идентификатор, как показано ниже.

Мы можем использовать DateTime.Now.Ticks и Guid.NewGuid(). ToString() для объединения и создания уникального идентификатора.

По мере добавления DateTime.Now.Ticks мы можем узнать дату и время в секундах, в которых создан уникальный идентификатор.

См. код.

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.

Мы можем даже взять часть тиков в уникальный идентификатор и проверить дату и время позже для будущих ссылок.

Вы можете прикрепить уникальный идентификатор, созданный для имени файла, или его можно использовать для создания уникального идентификатора сеанса для входа в систему для входа в систему для нашего приложения или веб-сайта.

Ответ 14

Если вы хотите иметь дату, время, минуты и т.д., вы можете использовать статическую переменную. Добавьте значение этой переменной в имя файла. Вы можете запустить счетчик с 0 и приращением, когда вы создали файл. Таким образом, имя файла будет уникальным, так как у вас есть секунды и в файле.

Ответ 15

Я обычно делаю что-то в этом направлении:

  • начинаем с имени файла стека (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 ;
    }

}

Вы можете настроить алгоритм (всегда используйте все возможные компоненты для имени файла). Зависит от контекста. Если бы я создавал файлы журналов, например, я мог бы захотеть повернуть из своего существования, вы бы хотели, чтобы все они делили один и тот же шаблон с именем.

Код не идеален (нет проверок на данные, переданные, например,). И алгоритм не идеален (если вы заполняете права на жесткий диск или разрешения на встречу, фактические ошибки ввода-вывода или другие ошибки файловой системы, например, это будет зависать, как он есть, в бесконечном цикле).

Ответ 16

Я заканчиваю конкатенирование GUID с помощью строки Day Month Year Second Millisecond, и я думаю, что это решение довольно хорошо в моем сценарии

Ответ 18

Я написал класс специально для этого. Он инициализируется "базовой" частью (по умолчанию используется метка времени с точностью до минуты) и после этого добавляет буквы для создания уникальных имен. Таким образом, если первый сгенерированный штамп будет 1907101215a, второй будет 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 {0})",
                      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();
    }
  }
}