Programming

C #에서 고유 한 파일 이름을 생성하는 방법

procodes 2020. 7. 10. 21:48
반응형

C #에서 고유 한 파일 이름을 생성하는 방법


하드 드라이브에 저장할 파일의 고유 이름을 생성하는 알고리즘을 구현했습니다. 내가 추가하고 있습니다 DateTime: Hours, Minutes, Second and Milliseconds 하지만 한 번에 여러 파일을 업로드하기 때문에 여전히 중복 된 파일 이름을 생성합니다.

파일이 하드 드라이브에 저장되도록 고유 한 이름을 생성하여 2 개의 파일이 동일하지 않도록하는 가장 좋은 솔루션은 무엇입니까?


가독성이 중요하지 않으면 GUID를 사용하십시오 .

예 :

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

또는 더 짧은 :

var myUniqueFileName = $@"{Guid.NewGuid()}.txt";

내 프로그램에서 읽을 수있는 이름 ( "Image1.png".. "Image10.png")을 생성하기 위해 예를 들어 10 번 시도하는 경우가 있는데 (파일이 이미 존재하기 때문에) 실패하면 GUID로 돌아갑니다.

최신 정보:

최근 DateTime.Now.Ticks에는 GUID 대신 사용 했습니다.

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

또는

var myUniqueFileName = $@"{DateTime.Now.Ticks}.txt";

나에게 이점은 이것이 GUID에 비해 더 짧고 "더욱보기 좋은"파일 이름을 생성한다는 것입니다.

경우에 따라 (예 : 매우 짧은 시간에 임의의 이름을 많이 생성 할 때) 고유하지 않은 값을 만들 수 있습니다 .

다른 컴퓨터로 파일을 전송할 때에도 파일 이름이 고유한지 확인하려면 GUID를 고수하십시오.


사용하다

Path.GetTempFileName()

또는 새로운 GUID ()를 사용하십시오.

MSDN의 Path.GetTempFilename () .


System.IO.Path.GetRandomFileName()

MSDN의 Path.GetRandomFileName () .


파일 이름의 가독성이 중요하지 않은 경우 많은 사람들이 제안한 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 자입니다.


다음은 제공된 원본을 기준으로 읽을 수있는 고유 한 파일 이름을 반환하는 알고리즘입니다. 원본 파일이 있으면 존재하지 않는 파일을 찾을 때까지 파일 이름에 색인을 추가하려고합니다. 충돌을 확인하기 위해 기존 파일 이름을 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();
    }
}

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

  1. 일반적인 프로세스에 따라 타임 스탬프가 지정된 파일 이름을 만듭니다.
  2. 파일 이름이 있는지 확인하십시오
  3. 거짓-파일 저장
  4. True-카운터에 추가 문자를 파일에 추가
  5. 2 단계로 이동

사용자 정의 방법없이 고유 한 파일 이름이 자동으로 생성되도록 할 수 있습니다. 다만 다음과 같은 사용 StorageFolder 클래스 또는 StorageFile 클래스 . 여기서 핵심은 다음 CreationCollisionOption.GenerateUniqueNameNameCollisionOption.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);

다음 코드를 사용하고 정상적으로 작동합니다. 이것이 도움이 되길 바랍니다.

타임 스탬프를 사용하여 고유 한 파일 이름으로 시작합니다.

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

C # 코드-

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

파일 이름에 날짜 시간 소인이 필요합니까?

파일 이름을 GUID로 만들 수 있습니다.


Guid.NewGuid()GUID를 생성하고 파일 이름 (또는 원하는 경우 타임 스탬프와 함께 파일 이름의 일부)으로 사용하는 방법은 어떻습니까 ?


파일 확장명 앞에 시퀀스 번호를 추가하여 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);
}

아래와 같이 고유 ID를 만들 수없는 이유는 무엇입니까?

DateTime.Now.Ticks와 Guid.NewGuid (). ToString ()을 사용하여 서로 결합하고 고유 한 ID를 만들 수 있습니다.

DateTime.Now.Ticks가 추가되면 고유 ID가 생성 된 날짜 및 시간 (초)을 확인할 수 있습니다.

코드를 참조하십시오.

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.

고유 ID로 진드기의 일부를 취하고 나중에 참조 할 수 있도록 날짜와 시간을 확인할 수도 있습니다.

파일 이름에 생성 된 고유 ID를 첨부하거나 응용 프로그램 또는 웹 사이트에 사용자의 로그인 로그 아웃을위한 고유 세션 ID를 생성하는 데 사용할 수 있습니다.


날짜 시간, 시간, 분 등을 원하면 정적 변수를 사용할 수 있습니다. 이 변수의 값을 파일 이름에 추가하십시오. 카운터를 0으로 시작하고 파일을 만들 때 증분 할 수 있습니다. 이렇게하면 파일에도 초가 있기 때문에 파일 이름이 고유해야합니다.


나는 보통이 라인을 따라 무언가를한다 :

  • 스템 파일 이름으로 시작 ( 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 ;
    }

}

알고리즘을 조정하고 싶을 수도 있습니다 (예를 들어 항상 가능한 모든 구성 요소를 파일 이름에 사용하십시오). 컨텍스트에 따라 달라집니다. 예를 들어 로그 파일을 만드는 중이라면 존재하지 않기를 원한다면 모두 동일한 패턴을 이름과 공유하기를 원할 것입니다.

코드가 완벽하지 않습니다 (예 : 전달 된 데이터를 확인하지 않음). 그리고 알고리즘이 완벽하지 않습니다 (하드 드라이브를 채우거나 권한, 실제 I / O 오류 또는 기타 파일 시스템 오류가 발생하면 무한 루프에서 정지됩니다).


Day Month Year Second Millisecond 문자열로 GUID를 연결 하고이 솔루션이 내 시나리오에서 상당히 좋다고 생각합니다.


Random.Next ()를 사용하여 난수를 생성 할 수도 있습니다. MSDN 링크를 볼 수 있습니다 : http://msdn.microsoft.com/en-us/library/9b3ta19y.aspx


나는 이것을하기 위해 특별히 수업을 썼다. "기본"부분 (기본값은 1 분 단위의 정확한 타임 스탬프)으로 초기화 된 후 고유 한 이름을 만들기 위해 문자를 추가합니다. 따라서 생성 된 첫 번째 스탬프가 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 {1})",
                      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();
    }
  }
}

참고 URL : https://stackoverflow.com/questions/4657974/how-to-generate-unique-file-names-in-c-sharp

반응형