C #: Как бы вы сделали уникальное имя файла, добавив число?

Я хотел бы создать метод, который принимает либо имя файла в качествеstring илиFileInfo и добавляет увеличенное число к имени файла, если файл существует. Но я не могу понять, как сделать это хорошим способом.

Например, если у меня есть этот FileInfo

var file = new FileInfo(@"C:\file.ext");

Я хотел бы, чтобы метод дал мне новый FileInfo сC:\file 1.ext еслиC:\file.ext существовал, иC:\file 2.ext еслиC:\file 1.ext существовал и так далее. Что-то вроде этого:

public FileInfo MakeUnique(FileInfo fileInfo)
{
    if(fileInfo == null)
        throw new ArgumentNullException("fileInfo");
    if(!fileInfo.Exists)
        return fileInfo;

    // Somehow construct new filename from the one we have, test it, 
    // then do it again if necessary.
}
 Sam Saffron03 июл. 2009 г., 08:38
Об этом недавно спросили ... попробую откопать
 Sam Saffron03 июл. 2009 г., 08:50
@svish, посмотрите мой ответ: вы не могли бы найти этот вопрос, если бы не знали, что он существует, у него было очень загадочное описание
 Maxim Zaslavsky03 июл. 2009 г., 11:30
хи-хи просто вопрос, который я собирался задать.
 Svish03 июл. 2009 г., 08:49
ой. пытался найти его, но ничего не смог найти.

Ответы на вопрос(17)

Не очень, но у меня было это некоторое время:

private string getNextFileName(string fileName)
{
    string extension = Path.GetExtension(fileName);

    int i = 0;
    while (File.Exists(fileName))
    {
        if (i == 0)
            fileName = fileName.Replace(extension, "(" + ++i + ")" + extension);
        else
            fileName = fileName.Replace("(" + i + ")" + extension, "(" + ++i + ")" + extension);
    }

    return fileName;
}

Предполагая, что файлы уже существуют:

File.txt File(1).txt File(2).txt

вызов getNextFileName (& quot; File.txt & quot;) вернет & quot; File (3) .txt & quot ;.

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

Если формат вас не беспокоит, вы можете позвонить:

try{
    string tempFile=System.IO.Path.GetTempFileName();
    string file=System.IO.Path.GetFileName(tempFile);
    //use file
    System.IO.File.Delete(tempFile);
}catch(IOException ioe){
  //handle 
}catch(FileIOPermission fp){
  //handle
}

PS: - Пожалуйста, прочитайте больше об этом наMSDN Перед использованием.

 03 июл. 2009 г., 11:24
@Svish Я уже сказал, если "... формат вас не беспокоит ...". Да это не очень мило. Вы можете добавить его к своему имени файла TextFile + & quot; _ & quot; + tempFile. Определенно это не склонно к каким-либо условиям гонки.
 Svish03 июл. 2009 г., 10:09
Дело в том, что у меня уже есть имя файла для использования. Если бы я мог просто сделать это, это не было бы проблемой в первую очередь;)

Посмотрите на методы вДорожка класс, в частностиPath.GetFileNameWithoutExtension (), а такжеPath.GetExtension ().

Вы можете даже найтиPath.GetRandomFileName () полезно!

Edit:

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

Вот тот, который отделяет пронумерованный вопрос об именах от проверки файловой системы:

/// <summary>
/// Finds the next unused unique (numbered) filename.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <param name="inUse">Function that will determine if the name is already in use</param>
/// <returns>The original filename if it wasn't already used, or the filename with " (n)"
/// added to the name if the original filename is already in use.</returns>
private static string NextUniqueFilename(string fileName, Func<string, bool> inUse)
{
    if (!inUse(fileName))
    {
        // this filename has not been seen before, return it unmodified
        return fileName;
    }
    // this filename is already in use, add " (n)" to the end
    var name = Path.GetFileNameWithoutExtension(fileName);
    var extension = Path.GetExtension(fileName);
    if (name == null)
    {
        throw new Exception("File name without extension returned null.");
    }
    const int max = 9999;
    for (var i = 1; i < max; i++)
    {
        var nextUniqueFilename = string.Format("{0} ({1}){2}", name, i, extension);
        if (!inUse(nextUniqueFilename))
        {
            return nextUniqueFilename;
        }
    }
    throw new Exception(string.Format("Too many files by this name. Limit: {0}", max));
}

А вот как это можно назвать, если вы используете файловую систему

var safeName = NextUniqueFilename(filename, f => File.Exists(Path.Combine(folder, f)));
 Svish21 мар. 2012 г., 17:14
Конечно, но я предпочитаю не устанавливать произвольные ограничения, как это :) (Если бы я выбрасывал исключение, я бы использовал более конкретное, чемException хоть... ;)
 21 мар. 2012 г., 16:20
Ограничение зависит от вас, лично я не хотел, чтобы что-то попадало в бесконечный цикл, и для меня вряд ли когда-нибудь случится 10000 файлов ;-) Да, и кстати, вышеприведенное не имеет отношения к проблемам производительности, которые есть у других Определено, возможно, кто-то может сделать комбинированную версию.
 Svish21 мар. 2012 г., 16:27
Не видите, как это попадет в бесконечный цикл, если у вас не было бесконечного количества файлов: p
 Svish21 мар. 2012 г., 16:15
Это действительно точка с ограничением там? Есть ли у файловой системы такой лимит?
 21 мар. 2012 г., 16:46
Это может произойти из-за ошибки кодирования (например, передача вf => true или что-то более запутанное в качестве второго параметра). Достижение максимума будет проще для устранения неполадок, чем наблюдение за тем, как ваш процессор будет закреплен на 100%, и ожидание целочисленного переполнения.
/// <summary>
/// Create a unique filename for the given filename
/// </summary>
/// <param name="filename">A full filename, e.g., C:\temp\myfile.tmp</param>
/// <returns>A filename like C:\temp\myfile633822247336197902.tmp</returns>
public string GetUniqueFilename(string filename)
{
    string basename = Path.Combine(Path.GetDirectoryName(filename),
                                   Path.GetFileNameWithoutExtension(filename));
    string uniquefilename = string.Format("{0}{1}{2}",
                                            basename,
                                            DateTime.Now.Ticks,
                                            Path.GetExtension(filename));
    // Thread.Sleep(1); // To really prevent collisions, but usually not needed
    return uniquefilename;
}

КакDateTime.Ticks имеет разрешение 100 наносекундстолкновения крайне маловероятны. Тем не менее, Thread.Sleep (1) обеспечит это, но я сомневаюсь, что это необходимо

Решение Вопроса

Здесь много полезных советов. Я закончил тем, что использовал метод, написанныйМарк вответ на другой вопрос, Немного переформатировал его и добавил другой метод, чтобы сделать его немного проще для использования "извне". Вот результат:

private static string numberPattern = " ({0})";

public static string NextAvailableFilename(string path)
{
    // Short-cut if already available
    if (!File.Exists(path))
        return path;

    // If path has extension then insert the number pattern just before the extension and return next filename
    if (Path.HasExtension(path))
        return GetNextFilename(path.Insert(path.LastIndexOf(Path.GetExtension(path)), numberPattern));

    // Otherwise just append the pattern to the path and return next filename
    return GetNextFilename(path + numberPattern);
}

private static string GetNextFilename(string pattern)
{
    string tmp = string.Format(pattern, 1);
    if (tmp == pattern)
        throw new ArgumentException("The pattern must include an index place-holder", "pattern");

    if (!File.Exists(tmp))
        return tmp; // short-circuit if no matches

    int min = 1, max = 2; // min is inclusive, max is exclusive/untested

    while (File.Exists(string.Format(pattern, max)))
    {
        min = max;
        max *= 2;
    }

    while (max != min + 1)
    {
        int pivot = (max + min) / 2;
        if (File.Exists(string.Format(pattern, pivot)))
            min = pivot;
        else
            max = pivot;
    }

    return string.Format(pattern, max);
}

Пока только частично протестировал его, но обновлю, если я найду какие-либо ошибки с ним. (МаркКод работает хорошо!) Если вы обнаружите какие-либо проблемы с ним, пожалуйста, прокомментируйте или отредактируйте что-нибудь :)

 Svish03 июл. 2009 г., 15:19
@ Сэм: Спасибо. Деф приятно узнать. Мои папки, вероятно, даже не будут близки к этому, но приятно знать, что это будет эффективно, если это произойдет. Хороший полезный метод, чтобы иметь вид :)
 Svish11 апр. 2013 г., 23:09
Нет, это не должно быть, потому что эта функция ожидает, что путь будет таким, каким вы хотите. Не так, как файл случается. Конечно, вы можете настроить функцию, если хотите, чтобы она велась по-другому, но вам придется делать это самостоятельно;)
 03 июл. 2009 г., 13:25
обратите внимание, из моего бенчмаркинга видно, что если у вас в каталоге более 170 файлов, это быстрее, чем просто получить все файлы в папке и выполнить всю работу в памяти
 24 сент. 2013 г., 18:24
Мне жаль поднимать старый вопрос, но может ли кто-нибудь объяснить мне, как работает этот подход? Я не понимаю, как и почему GetNextFilename (шаблон строки) использует pivot, max и min.
 11 апр. 2013 г., 14:21
Что если NextAvailableFilename (& quot; MyFile (1) .txt & quot;)? Является ли результат "MyFile (1) (1) .txt"? Не должно ли это быть "MyFile (2) .txt"?

Это просто строковая операция; найдите в строке имени файла место, куда вы хотите вставить номер, и заново создайте новую строку с добавленным номером. Чтобы сделать его многоразовым, вы можете захотетьlook for число в этом месте, и разобрать его в целое число, чтобы вы могли увеличить его.

Обратите внимание, что в общем случае этот способ генерации уникального имени файла небезопасен; есть очевидныесостояние гонки опасности.

Могут быть готовые решения для этого на платформе, я не в курсе C #, поэтому я не могу помочь в этом.

Я сделал это так:

for (int i = 0; i <= 500; i++) //I suppose the number of files will not pass 500
        {       //Checks if C:\log\log+TheNumberOfTheFile+.txt exists...
            if (System.IO.File.Exists(@"C:\log\log"+conta_logs+".txt"))
            {
                conta_logs++;//If exists, then increment the counter
            }
            else
            {              //If not, then the file is created
                var file = System.IO.File.Create(@"C:\log\log" + conta_logs + ".txt");
                break; //When the file is created we LEAVE the *for* loop
            }
        }

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

Если вам нужно просто уникальное имя файла, то как насчет этого?

Path.GetRandomFileName()

Если проверить, существует ли файл слишком сложно, вы всегда можете просто добавить дату и время к имени файла, чтобы сделать его уникальным:

FileName.YYYYMMDD.HHMMSS

Может быть, даже добавить миллисекунды, если это необходимо.

 22 мая 2017 г., 07:27
И код C # для создания суффикса выглядит следующим образом:DateTime.UtcNow.ToString("yyyyMMddHHmmss") (с учетом регистра).
 04 июл. 2009 г., 19:47
Если odering не имеет значения, вы также можете использовать генерацию случайных чисел со счетчиком и pid.
 28 июн. 2016 г., 20:14
Вы можете просто изменить его на YYYYMMDD.HHMMSS, чтобы решить проблему с заказом ...
 Svish04 июл. 2014 г., 12:41
При заказеis важно, я бы точно не использовал этот формат даты ...
 03 июл. 2009 г., 08:47
Я успешно использовал эту технику. Если вы создаете файлы слишком быстро, вы рискуете столкнуться с именами, но если вы знаете, что вы не создаете несколько файлов в течение миллисекунд друг от друга, это прекрасно работает.

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

public static class FileInfoExtensions
{
    public static FileInfo MakeUnique(this FileInfo fileInfo)
    {
        if (fileInfo == null)
        {
            throw new ArgumentNullException("fileInfo");
        }

        string newfileName = new FileUtilities().GetNextFileName(fileInfo.FullName);
        return new FileInfo(newfileName);
    }
}

public class FileUtilities
{
    public string GetNextFileName(string fullFileName)
    {
        if (fullFileName == null)
        {
            throw new ArgumentNullException("fullFileName");
        }

        if (!File.Exists(fullFileName))
        {
            return fullFileName;
        }
        string baseFileName = Path.GetFileNameWithoutExtension(fullFileName);
        string ext = Path.GetExtension(fullFileName);

        string filePath = Path.GetDirectoryName(fullFileName);
        var numbersUsed = Directory.GetFiles(filePath, baseFileName + "*" + ext)
            .Select(x => Path.GetFileNameWithoutExtension(x).Substring(baseFileName.Length))
            .Select(x =>
                    {
                        int result;
                        return Int32.TryParse(x, out result) ? result : 0;
                    })
            .Distinct()
            .OrderBy(x => x)
            .ToList();

        var firstGap = numbersUsed
            .Select((x, i) => new { Index = i, Item = x })
            .FirstOrDefault(x => x.Index != x.Item);
        int numberToUse = firstGap != null ? firstGap.Item : numbersUsed.Count;
        return Path.Combine(filePath, baseFileName) + numberToUse + ext;
    }
}    
    private async Task<CloudBlockBlob> CreateBlockBlob(CloudBlobContainer container,  string blobNameToCreate)
    {
        var blockBlob = container.GetBlockBlobReference(blobNameToCreate);

        var i = 1;
        while (await blockBlob.ExistsAsync())
        {
            var newBlobNameToCreate = CreateRandomFileName(blobNameToCreate,i.ToString());
            blockBlob = container.GetBlockBlobReference(newBlobNameToCreate);
            i++;
        }

        return blockBlob;
    }



    private string CreateRandomFileName(string fileNameWithExtension, string prefix=null)
    {

        int fileExtPos = fileNameWithExtension.LastIndexOf(".", StringComparison.Ordinal);

        if (fileExtPos >= 0)
        {
            var ext = fileNameWithExtension.Substring(fileExtPos, fileNameWithExtension.Length - fileExtPos);
            var fileName = fileNameWithExtension.Substring(0, fileExtPos);

            return String.Format("{0}_{1}{2}", fileName, String.IsNullOrWhiteSpace(prefix) ? new Random().Next(int.MinValue, int.MaxValue).ToString():prefix,ext);
        }

        //This means there is no Extension for the file and its fine attaching random number at the end.
        return String.Format("{0}_{1}", fileNameWithExtension, new Random().Next(int.MinValue, int.MaxValue));
    }

Я использую этот код для создания последовательного имени файла _1, _2, _3 и т. Д. Каждый раз, когда файл существует в хранилище BLOB-объектов.

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

Примечание. Это уязвимо для условий гонки, поэтому, если у вас есть несколько потоков, создающих эти файлы,be careful.

Примечание 2: это не проверено.

public static FileInfo GetNextUniqueFile(string path)
{
    //if the given file doesn't exist, we're done
    if(!File.Exists(path))
        return new FileInfo(path);

    //split the path into parts
    string dirName = Path.GetDirectoryName(path);
    string fileName = Path.GetFileNameWithoutExtension(path);
    string fileExt = Path.GetExtension(path);

    //get the directory
    DirectoryInfo dir = new DirectoryInfo(dir);

    //get the list of existing files for this name and extension
    var existingFiles = dir.GetFiles(Path.ChangeExtension(fileName + " *", fileExt);

    //get the number strings from the existing files
    var NumberStrings = from file in existingFiles
                        select Path.GetFileNameWithoutExtension(file.Name)
                            .Remove(0, fileName.Length /*we remove the space too*/);

    //find the highest existing number
    int highestNumber = 0;

    foreach(var numberString in NumberStrings)
    {
        int tempNum;
        if(Int32.TryParse(numberString, out tempnum) && tempNum > highestNumber)
            highestNumber = tempNum;
    }

    //make the new FileInfo object
    string newFileName = fileName + " " + (highestNumber + 1).ToString();
    newFileName = Path.ChangeExtension(fileName, fileExt);

    return new FileInfo(Path.Combine(dirName, newFileName));
}
 Svish03 июл. 2009 г., 09:11
попытался прояснить вопрос. это был следующий файл, который не существует, который я хотел, но написал его немного криво: p
 03 июл. 2009 г., 09:12
Это то, что я понял, и мой ответ делает это. :)
public FileInfo MakeUnique(string path)
{            
    string dir = Path.GetDirectoryName(path);
    string fileName = Path.GetFileNameWithoutExtension(path);
    string fileExt = Path.GetExtension(path);

    for (int i = 1; ;++i) {
        if (!File.Exists(path))
            return new FileInfo(path);

        path = Path.Combine(dir, fileName + " " + i + fileExt);
    }
}

Очевидно, что это уязвимо для условий гонки, как отмечено в других ответах.

 03 июл. 2009 г., 08:42
Это действительно неэффективно для большого количества файлов, лучше использовать бинарный поиск
 03 июл. 2009 г., 08:43
Один из способов (помочь) устранить условие гонки - вместо этого вернуть открытый поток в файл, а не просто информацию о файле.
 30 сент. 2014 г., 20:45
Согласитесь с @nashwan, это приводит к нежелательным результатам.
 03 июл. 2009 г., 08:43
@ Сэм: Иди, напиши свое. Я буду спать;)
 29 янв. 2013 г., 18:06
-1 - Не знаю, почему у этого ответа столько взлетов, что он не сделает то, о чем спрашивал вопрос. Вместо того, чтобы увеличивать и заменять число в имени файла, оно просто добавляет его (код ничего не делает, чтобы удалить исходное число и заменить его на увеличенное число).

Этот метод добавит индекс к существующему файлу при необходимости:

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

static public string AddIndexToFileNameIfNeeded(string sFileNameWithPath)
{
    string sFileNameWithIndex = sFileNameWithPath;

    while (File.Exists(sFileNameWithIndex)) // run in while scoop so if after adding an index the the file name the new file name exist, run again until find a unused file name
    { // File exist, need to add index

        string sFilePath = Path.GetDirectoryName(sFileNameWithIndex);
        string sFileName = Path.GetFileNameWithoutExtension(sFileNameWithIndex);
        string sFileExtension = Path.GetExtension(sFileNameWithIndex);

        if (sFileName.Contains('_'))
        { // Need to increase the existing index by one or add first index

            int iIndexOfUnderscore = sFileName.LastIndexOf('_');
            string sContentAfterUnderscore = sFileName.Substring(iIndexOfUnderscore + 1);

            // check if content after last underscore is a number, if so increase index by one, if not add the number _01
            int iCurrentIndex;
            bool bIsContentAfterLastUnderscoreIsNumber = int.TryParse(sContentAfterUnderscore, out iCurrentIndex);
            if (bIsContentAfterLastUnderscoreIsNumber)
            {
                iCurrentIndex++;
                string sContentBeforUnderscore = sFileName.Substring(0, iIndexOfUnderscore);

                sFileName = sContentBeforUnderscore + "_" + iCurrentIndex.ToString("000");
                sFileNameWithIndex = sFilePath + "\\" + sFileName + sFileExtension;
            }
            else
            {
                sFileNameWithIndex = sFilePath + "\\" + sFileName + "_001" + sFileExtension;
            }
        }
        else
        { // No underscore in file name. Simple add first index
            sFileNameWithIndex = sFilePath + "\\" + sFileName + "_001" + sFileExtension;
        }
    }

    return sFileNameWithIndex;
}

Надеюсь, что эта итеративная функция может помочь. Он отлично работает для меня.

public string getUniqueFileName(int i, string filepath, string filename)
    {
        string path = Path.Combine(filepath, filename);
        if (System.IO.File.Exists(path))
        {
            string name = Path.GetFileNameWithoutExtension(filename);
            string ext = Path.GetExtension(filename);
            i++;
            filename = getUniqueFileName(i, filepath, name + "_" + i + ext);
        }
        return filename; 
    }

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

 03 июл. 2009 г., 10:15
Я поражен, что никто другой не сказал этого, все остальное было бы пустой тратой усилий (если вы действительно не хотите, чтобы ваши временные имена файлов выглядели довольно ... но почему ?!)
 Svish03 июл. 2009 г., 10:07
Лично я нахожу GUID невероятно уродливым. Но хорошая идея.
 Svish03 июл. 2009 г., 10:17
Потому что файлы не временные? У меня уже есть имя, которое нужно. Мне просто нужно добавить к нему номер, если файл с таким именем уже существует, чтобы я не перезаписывал уже существующий.
 09 мар. 2011 г., 14:50
Хороший пример того, где это необходимо, - это когда Visual Studio создает новый класс. Если Class1.cs уже существует, он создает Class2.cs и т. Д. Для VS было бы смешно создавать новый класс с GUID в качестве имени.

Ваш ответ на вопрос