[C#] 바이트 배열을 16 진수 문자열로 어떻게 변환합니까?

바이트 배열을 16 진수 문자열로 어떻게 변환 할 수 있습니까?



답변

어느 한 쪽:

public static string ByteArrayToString(byte[] ba)
{
  StringBuilder hex = new StringBuilder(ba.Length * 2);
  foreach (byte b in ba)
    hex.AppendFormat("{0:x2}", b);
  return hex.ToString();
}

또는:

public static string ByteArrayToString(byte[] ba)
{
  return BitConverter.ToString(ba).Replace("-","");
}

여기에 더 많은 변형이 있습니다 (예 : here) .

역변환은 다음과 같습니다.

public static byte[] StringToByteArray(String hex)
{
  int NumberChars = hex.Length;
  byte[] bytes = new byte[NumberChars / 2];
  for (int i = 0; i < NumberChars; i += 2)
    bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
  return bytes;
}

Substring와 함께 사용하는 것이 가장 좋습니다 Convert.ToByte. 자세한 내용은 이 답변 을 참조하십시오. 더 나은 성능이 필요한 경우, Convert.ToByte삭제하기 전에 피해야합니다 SubString.


답변

성능 분석

참고 : 2015-08-20 기준 새로운 지도자.

나는 몇 가지 조잡한 Stopwatch성능 테스트, 임의 문장 실행 (n = 61, 1000 반복) 및 Project Gutenburg 텍스트 실행 (n = 1,238,957, 150 반복)을 통해 다양한 변환 방법을 각각 실행했습니다. 대략 가장 빠른 결과에서 가장 느린 결과까지의 결과는 다음과 같습니다. 모든 측정은 눈금 ( 10,000 틱 = 1ms )으로 이루어지며 모든 상대 음표는 [가장 느린] StringBuilder구현 과 비교됩니다 . 사용 된 코드는 아래 또는 테스트 프레임 워크 저장소를 참조하십시오 .

기권

경고 : 구체적인 통계를 위해이 통계에 의존하지 마십시오. 그것들은 단순히 샘플 데이터의 샘플 실행입니다. 최고 수준의 성능이 실제로 필요한 경우 프로덕션 요구를 나타내는 환경에서 사용할 데이터를 나타내는 데이터를 사용하여 이러한 방법을 테스트하십시오.

결과

조회 테이블이 바이트 오버 조작을 주도했습니다. 기본적으로 어떤 주어진 니블이나 바이트가 16 진수인지 미리 계산하는 형태가 있습니다. 그런 다음 데이터를 훑어 보면 다음 부분을 찾아 16 진 문자열이 무엇인지 확인하면됩니다. 그런 다음 해당 값이 어떤 방식으로 결과 문자열 출력에 추가됩니다. 오랜 시간 바이트 조작의 경우 일부 개발자가 읽기 어려울 가능성이 가장 높은 방법이었습니다.

최선의 방법은 여전히 ​​대표적인 데이터를 찾아 프로덕션과 같은 환경에서 시도하는 것입니다. 다른 메모리 제약 조건이있는 경우 더 빠르지 만 더 많은 메모리를 소비하는 방법에 대한 할당량이 적은 방법을 선호 할 수 있습니다.

테스트 코드

내가 사용한 테스트 코드로 자유롭게 연주하십시오. 여기에 버전이 포함되어 있지만 리포지토리 를 복제하고 원하는 방식으로 추가하십시오. 흥미있는 것이 있거나 사용하는 테스트 프레임 워크를 향상 시키려면 풀 요청을 제출하십시오.

  1. 새 정적 메소드 ( Func<byte[], string>)를 /Tests/ConvertByteArrayToHexString/Test.cs에 추가하십시오 .
  2. 해당 메소드의 이름을 TestCandidates동일한 클래스 의 리턴 값에 추가하십시오 .
  3. GenerateTestInput동일한 클래스에서 주석을 토글하여 원하는 입력 버전, 문장 또는 텍스트를 실행하고 있는지 확인하십시오 .
  4. 적중 F5하고 출력을 기다립니다 (/ bin 폴더에도 HTML 덤프가 생성됨).
static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
    return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
    return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
    string hex = BitConverter.ToString(bytes);
    return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.Append(b.ToString("X2"));
    return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.AppendFormat("{0:X2}", b);
    return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    byte b;
    for (int i = 0; i < bytes.Length; i++) {
        b = ((byte)(bytes[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(bytes[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    int b;
    for (int i = 0; i < bytes.Length; i++) {
        b = bytes[i] >> 4;
        c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
        b = bytes[i] & 0xF;
        c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
    }
    return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
    SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
    return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    string hexAlphabet = "0123456789ABCDEF";
    foreach (byte b in bytes) {
        result.Append(hexAlphabet[(int)(b >> 4)]);
        result.Append(hexAlphabet[(int)(b & 0xF)]);
    }
    return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result) {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++) {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
    string s = i.ToString("X2");
    return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
    var result = new char[bytes.Length * 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        var val = _Lookup32[bytes[i]];
        result[2*i] = (char)val;
        result[2*i + 1] = (char) (val >> 16);
    }
    return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
    string[] hexStringTable = new string[] {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
        "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
        "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
        "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
        "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
        "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
        "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
    };
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes) {
        result.Append(hexStringTable[b]);
    }
    return result.ToString();
}

업데이트 (2010-01-13)

Waleed의 분석에 대한 답변을 추가했습니다. 꽤 빠릅니다.

업데이트 (2011-10-05)

string.Concat Array.ConvertAll완전성을위한 변형이 추가 되었습니다 (.NET 4.0 필요). 파와 string.Join버전.

업데이트 (2012-02-05)

테스트 저장소에는와 같은 추가 변종이 포함되어 StringBuilder.Append(b.ToString("X2"))있습니다. 결과가 전혀 화 나지 않습니다. 예를 들어 foreach보다 빠르지 {IEnumerable}.AggregateBitConverter여전히 승리합니다.

업데이트 (2012-04-03)

Mykroft의 SoapHexBinary분석에 대한 답변을 추가 하여 3 위를 차지했습니다.

업데이트 (2013-01-15)

InChaos의 바이트 조작 응답이 추가되었습니다 (대부분의 텍스트 블록에서 큰 차이로).

업데이트 (2013-05-23)

Nathan Moinvaziri의 조회 답변과 Brian Lambert의 블로그 변형을 추가했습니다. 둘 다 빠르지 만 내가 사용한 테스트 머신 (AMD Phenom 9750)에서 주도권을 잡지 않았습니다.

업데이트 (2014-07-31)

@CodesInChaos의 새로운 바이트 기반 조회 응답이 추가되었습니다. 문장 테스트와 전체 텍스트 테스트 모두에서 주도적 인 역할을 한 것으로 보입니다.

업데이트 (2015-08-20)

답변의 repo에 airbreather의 최적화 및 unsafe변형을 추가 했습니다 . 안전하지 않은 게임에서 플레이하려면 짧은 문자열과 큰 텍스트 모두에서 이전의 최고 우승자에 비해 큰 성능 향상을 얻을 수 있습니다.


답변

SoapHexBinary 라는 클래스 가 있으며 원하는 것을 정확하게 수행합니다.

using System.Runtime.Remoting.Metadata.W3cXsd2001;

public static byte[] GetStringToBytes(string value)
{
    SoapHexBinary shb = SoapHexBinary.Parse(value);
    return shb.Value;
}

public static string GetBytesToString(byte[] value)
{
    SoapHexBinary shb = new SoapHexBinary(value);
    return shb.ToString();
}


답변

암호화 코드를 작성할 때 데이터 종속 분기 및 테이블 조회를 피하여 런타임이 데이터에 의존하지 않도록하는 것이 일반적입니다. 데이터 종속 타이밍은 부 채널 공격을 유발할 수 있기 때문입니다.

꽤 빠릅니다.

static string ByteToHexBitFiddle(byte[] bytes)
{
    char[] c = new char[bytes.Length * 2];
    int b;
    for (int i = 0; i < bytes.Length; i++) {
        b = bytes[i] >> 4;
        c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
        b = bytes[i] & 0xF;
        c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
    }
    return new string(c);
}

Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn


모든 희망을 버리고 여기에 들어가는 너희

이상한 비트 피들 링에 대한 설명 :

  1. bytes[i] >> 4바이트의 높은 니블을
    bytes[i] & 0xF추출 바이트 의 낮은 니블을 추출
  2. b - 10
    < 0값을 b < 10소수점 자리가 될 것
    입니다 >= 0b > 10편지가 될 것이다, AF.
  3. i >> 31부호있는 32 비트 정수를 사용하면 부호 확장 덕분에 부호가 추출됩니다. 그것은 될 것입니다 -1위해 i < 00위해 i >= 0.
  4. 2)와 3)을 결합하면 문자와 숫자에 대한 (b-10)>>31것임을 알 수 있습니다 .0-1
  5. 문자의 경우를 보면, 지난 피가수가되고 0, 그리고 b우리가 그것을 매핑 할 10에서 15 사이에 A(65)에 F55를 추가하는 것을 의미하는 (70) ( 'A'-10).
  6. 숫자의 경우를 살펴보면 마지막 summand를 조정하여 b0에서 9까지의 범위에서 0(48)에서 9(57) 까지의 범위를 매핑 하려고합니다 . 이것은 -7 ( '0' - 55) 이되어야한다는 것을 의미합니다 .
    이제 우리는 7을 곱할 수 있습니다. 그러나 -1은 모든 비트가 1로 표시 & -7되기 때문에 대신 (0 & -7) == 0and 를 사용할 수 있습니다 (-1 & -7) == -7.

몇 가지 추가 고려 사항 :

  • c측정 결과 값 i이 저렴 하다는 것을 보여주기 때문에 두 번째 루프 변수를 사용하여 인덱싱하지 않았습니다 .
  • i < bytes.Length루프의 상한으로 정확하게 사용하면 JITter가 on에 대한 경계 검사를 제거 할 수 bytes[i]있으므로 해당 변형을 선택했습니다.
  • b정수를 만들면 바이트를 불필요하게 변환 할 수 있습니다.

답변

보다 유연 BitConverter하지만 1990 년대 스타일의 명시 적 루프를 원하지 않는 경우 다음을 수행 할 수 있습니다.

String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));

또는 .NET 4.0을 사용하는 경우 :

String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));

후자는 원래 게시물에 대한 의견에서 나온 것입니다.


답변

다른 조회 테이블 기반 접근 방식. 이것은 니블 당 룩업 테이블 대신 각 바이트마다 하나의 룩업 테이블을 사용합니다.

private static readonly uint[] _lookup32 = CreateLookup32();

private static uint[] CreateLookup32()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s=i.ToString("X2");
        result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
    }
    return result;
}

private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
    var lookup32 = _lookup32;
    var result = new char[bytes.Length * 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        var val = lookup32[bytes[i]];
        result[2*i] = (char)val;
        result[2*i + 1] = (char) (val >> 16);
    }
    return new string(result);
}

나는 또한 사용하여이의 변형을 테스트 ushort, struct{char X1, X2}, struct{byte X1, X2}룩업 테이블에.

컴파일 대상 (x86, X64)에 따라 성능이 거의 같거나이 변형보다 약간 느립니다.


그리고 더 높은 성능을 위해 unsafe형제는 다음과 같습니다.

private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();

private static uint[] CreateLookup32Unsafe()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s=i.ToString("X2");
        if(BitConverter.IsLittleEndian)
            result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
        else
            result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
    }
    return result;
}

public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
    var lookupP = _lookup32UnsafeP;
    var result = new char[bytes.Length * 2];
    fixed(byte* bytesP = bytes)
    fixed (char* resultP = result)
    {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++)
        {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return new string(result);
}

또는 문자열에 직접 쓰는 것이 허용되는 것으로 간주되는 경우 :

public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result)
    {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++)
        {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}


답변

BitConverter.ToString 메서드를 사용할 수 있습니다.

byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));

산출:

00-01-02-04-08-10-20-40-80-FF

추가 정보 : BitConverter.ToString 메서드 (Byte [])