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

41

Я хотел бы создать метод, который принимает либо имя файла в качестве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.
}
хи-хи просто вопрос, который я собирался задать. Maxim Zaslavsky
@svish, посмотрите мой ответ: вы не могли бы найти этот вопрос, если бы не знали, что он существует, у него было очень загадочное описание Sam Saffron
ой. пытался найти его, но ничего не смог найти. Svish
Об этом недавно спросили ... попробую откопать Sam Saffron

Ваш Ответ

17   ответов
5

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

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 Перед использованием.

@Svish Я уже сказал, если "... формат вас не беспокоит ...". Да это не очень мило. Вы можете добавить его к своему имени файла TextFile + & quot; _ & quot; + tempFile. Определенно это не склонно к каким-либо условиям гонки.
Дело в том, что у меня уже есть имя файла для использования. Если бы я мог просто сделать это, это не было бы проблемой в первую очередь;) Svish
4
/// <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) обеспечит это, но я сомневаюсь, что это необходимо

26

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

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

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

обратите внимание, из моего бенчмаркинга видно, что если у вас в каталоге более 170 файлов, это быстрее, чем просто получить все файлы в папке и выполнить всю работу в памяти
Нет, это не должно быть, потому что эта функция ожидает, что путь будет таким, каким вы хотите. Не так, как файл случается. Конечно, вы можете настроить функцию, если хотите, чтобы она велась по-другому, но вам придется делать это самостоятельно;) Svish
@ Сэм: Спасибо. Деф приятно узнать. Мои папки, вероятно, даже не будут близки к этому, но приятно знать, что это будет эффективно, если это произойдет. Хороший полезный метод, чтобы иметь вид :) Svish
Мне жаль поднимать старый вопрос, но может ли кто-нибудь объяснить мне, как работает этот подход? Я не понимаю, как и почему GetNextFilename (шаблон строки) использует pivot, max и min.
Что если NextAvailableFilename (& quot; MyFile (1) .txt & quot;)? Является ли результат "MyFile (1) (1) .txt"? Не должно ли это быть "MyFile (2) .txt"?
0

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

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

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

0

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

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

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

0

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

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

Edit:

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

15

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

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

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

0

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

Path.GetRandomFileName()
1

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

/// <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)));
Конечно, но я предпочитаю не устанавливать произвольные ограничения, как это :) (Если бы я выбрасывал исключение, я бы использовал более конкретное, чемException хоть... ;) Svish
Ограничение зависит от вас, лично я не хотел, чтобы что-то попадало в бесконечный цикл, и для меня вряд ли когда-нибудь случится 10000 файлов ;-) Да, и кстати, вышеприведенное не имеет отношения к проблемам производительности, которые есть у других Определено, возможно, кто-то может сделать комбинированную версию.
Не видите, как это попадет в бесконечный цикл, если у вас не было бесконечного количества файлов: p Svish
Это действительно точка с ограничением там? Есть ли у файловой системы такой лимит? Svish
Это может произойти из-за ошибки кодирования (например, передача вf => true или что-то более запутанное в качестве второго параметра). Достижение максимума будет проще для устранения неполадок, чем наблюдение за тем, как ваш процессор будет закреплен на 100%, и ожидание целочисленного переполнения.
11

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

FileName.YYYYMMDD.HHMMSS

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

И код C # для создания суффикса выглядит следующим образом:DateTime.UtcNow.ToString("yyyyMMddHHmmss") (с учетом регистра).
Если odering не имеет значения, вы также можете использовать генерацию случайных чисел со счетчиком и pid.
Вы можете просто изменить его на YYYYMMDD.HHMMSS, чтобы решить проблему с заказом ...
При заказеis важно, я бы точно не использовал этот формат даты ... Svish
Я успешно использовал эту технику. Если вы создаете файлы слишком быстро, вы рискуете столкнуться с именами, но если вы знаете, что вы не создаете несколько файлов в течение миллисекунд друг от друга, это прекрасно работает.
1

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

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;
    }
}    
0
    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-объектов.

1

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

Примечание. Это уязвимо для условий гонки, поэтому, если у вас есть несколько потоков, создающих эти файлы,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));
}
Это то, что я понял, и мой ответ делает это. :)
попытался прояснить вопрос. это был следующий файл, который не существует, который я хотел, но написал его немного криво: p Svish
47
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);
    }
}

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

Это действительно неэффективно для большого количества файлов, лучше использовать бинарный поиск
Один из способов (помочь) устранить условие гонки - вместо этого вернуть открытый поток в файл, а не просто информацию о файле.
Согласитесь с @nashwan, это приводит к нежелательным результатам.
@ Сэм: Иди, напиши свое. Я буду спать;)
-1 - Не знаю, почему у этого ответа столько взлетов, что он не сделает то, о чем спрашивал вопрос. Вместо того, чтобы увеличивать и заменять число в имени файла, оно просто добавляет его (код ничего не делает, чтобы удалить исходное число и заменить его на увеличенное число).
0

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

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

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

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

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

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

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

Похожие вопросы