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

StackOverflow https://stackoverflow.com/questions/1078003

  •  21-08-2019
  •  | 
  •  

Вопрос

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

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

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

Я хотел бы, чтобы метод предоставил мне новую FileInfo с помощью C:\файл 1.ext если C:\file.extсуществовал, и C:\файл 2.ext если C:\файл 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.
}
Это было полезно?

Решение 2

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

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

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

Другие советы

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

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

Некрасиво, но у меня уже давно есть вот это:

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

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

  • Файл.txt
  • Файл(1).txt
  • Файл(2).txt

вызов getNextFileName("File.txt") вернет "File(3).txt".

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

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

ИмяФайла.ГГГГММДД.ЧЧММСС

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

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

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

/// <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) обеспечит это, но я сомневаюсь, что это необходимо.

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

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

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

Заметка 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));
}

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

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

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

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

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

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

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

Взгляните на методы, описанные в Путь класс, конкретно Путь.GetFileNameWithoutExtension(), и Путь.GetExtension().

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

Редактировать:

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

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

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

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

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

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

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

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()
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top