Как создать уникальные имена файлов в С#
Я реализовал алгоритм, который будет генерировать уникальные имена для файлов, которые будут сохраняться на жестком диске. Я добавляю 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, если вы действительно уверены, что имена файлов уникальны, даже если они переносятся на другие компьютеры.
Ответ 2
Использование
Path.GetTempFileName()
или используйте новый GUID().
Path.GetTempFilename() в MSDN.
Ответ 3
System.IO.Path.GetRandomFileName()
Path.GetRandomFileName() в MSDN.
Ответ 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, и я думаю, что это решение довольно хорошо в моем сценарии
Ответ 17
вы можете использовать Random.Next() также для генерации случайного числа. вы можете увидеть ссылку MSDN: http://msdn.microsoft.com/en-us/library/9b3ta19y.aspx
Ответ 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();
}
}
}