programing

바이트 배열을 16진수 문자열로 변환하거나 그 반대로 변환하는 방법은 무엇입니까?

abcjava 2023. 5. 26. 20:22
반응형

바이트 배열을 16진수 문자열로 변환하거나 그 반대로 변환하는 방법은 무엇입니까?

바이트 배열을 16진수 문자열로 변환하거나 그 반대로 변환하려면 어떻게 해야 합니까?

.NET 5부터 사용할 수 있습니다.
역방향 연산 방법도 있습니다.


이전 버전의 .NET의 경우 다음 중 하나를 사용할 수 있습니다.

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

를 들어, 여기에는 훨씬 더 다양한 종류의 작업이 있습니다.

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

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

성능 분석

참고: 2015-08-20 시점의 새로운 리더.

저는 다양한 변환 방법을 약간의 조잡한 방법으로 실행했습니다.Stopwatch성능 테스트, 무작위 문장으로 실행(n=61, 1000번 반복) 및 프로젝트 구텐부르크 텍스트로 실행(n=1,238,957,150번 반복).다음은 대략 가장 빠른 것부터 가장 느린 것까지 결과입니다.모든 측정값은 눈금으로 표시되며(10,000 눈금 = 1ms) 모든 관련 노트는 [가장 느린] 값과 비교됩니다.StringBuilder실행.사용된 코드에 대해서는 아래 또는 테스트 프레임워크 repo를 참조하십시오. 여기서 이 코드를 실행하기 위한 코드를 유지 관리합니다.

부인

경고: 구체적인 내용은 이러한 통계에 의존하지 마십시오. 이 통계는 샘플 데이터의 샘플 실행에 불과합니다.최고의 성능이 정말로 필요한 경우, 사용할 데이터를 사용하여 프로덕션 요구를 대표하는 환경에서 이러한 방법을 테스트하십시오.

결과.

조회 테이블이 바이트 조작보다 앞서 있습니다.기본적으로, 주어진 니블이나 바이트가 16진수인 것을 사전 계산하는 어떤 형태가 있습니다.그런 다음 데이터를 복사할 때 다음 부분을 찾아 16진수 문자열이 무엇인지 확인하기만 하면 됩니다.그런 다음 이 값은 어떤 방식으로든 결과 문자열 출력에 추가됩니다.오랫동안 일부 개발자가 읽기 어려울 수 있는 바이트 조작은 최고 성능의 접근 방식이었습니다.

여전히 대표적인 데이터를 찾아서 프로덕션 환경에서 사용해 보는 것이 가장 좋습니다.메모리 제약 조건이 다른 경우 더 빠르지만 더 많은 메모리를 사용하는 방법보다 할당 수가 적은 방법을 선호할 수 있습니다.

테스트 코드

제가 사용한 테스트 코드를 자유롭게 사용하세요.여기에 버전이 포함되어 있지만, 자유롭게 레포를 복제하고 자신만의 메소드를 추가할 수 있습니다.흥미로운 점이 있거나 사용하는 테스트 프레임워크를 개선하기 위해 도움이 필요한 경우 풀 요청을 제출하십시오.

  1. method를 합니다.Func<byte[], string>/Tests/ByteArrayToHexString/Test.cs 파일입니다.
  2. 을 당메서이에추다니합가에 합니다.TestCandidates같은 클래스의 반환 값입니다.
  3. 에서 주석을 전환하여 원하는 입력 버전(문장 또는 텍스트)을 실행하고 있는지 확인합니다.GenerateTestInput같은 반에
  4. 를 누르고 출력을 기다립니다(HTML 덤프도 /bin 폴더에 생성됨).
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}.Aggregate를 들어, 하지만 예를들면, 하만지.BitConverter여전히 이깁니다.

업데이트(2012-04-03)

의 Mykroft의 추가 SoapHexBinary3위를 차지한 분석에 대한 답변.

업데이트(2013-01-15)

(큰 텍스트 블록에서 큰 차이로) 1위를 차지한 혼돈의 바이트 조작 답변에 코드 추가.

업데이트(2013-05-23)

Nathan Moinvaziri의 검색 답변과 Brian Lambert의 블로그 변형 추가.둘 다 다소 빠르지만, 내가 사용한 테스트 기계(AMD Phenom 9750)에서 주도권을 잡지 못합니다.

업데이트(2014-07-31)

@CodesInChaos의 새로운 바이트 기반 조회 응답에 추가되었습니다.문장 시험과 전문 시험 모두에서 선두를 차지한 것으로 보입니다.

업데이트 (2015-08-20)

공기 호흡기 최적화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);
}

프응글루이엠글루나프 크툴후 롤리예흐와그나글프타그


모든 희망을 버려라, 여기 들어오는 자들아.

이상한 장난에 대한 설명:

  1. bytes[i] >> 4합니다.
    bytes[i] & 0xF합니다.
  2. b - 10
    이라< 0 가를위 하여치의 .b < 10가 될 입니다.
    이라>= 0 가를위 하여치의 .b > 10그것은 편지가 될 것입니다.AF.
  3. 용사를 합니다.i >> 31부호 확장 기능 덕분에 부호가 있는 32비트 정수에서 부호를 추출합니다.그럴 것이다.-1위해서i < 0그리고.0위해서i >= 0.
  4. 와 3하면 2)와 3)의 조합을 알 수 .(b-10)>>31▁▁be 될 것입니다.0와 文字에 -1자리수에 대하여
  5. , 는 편의경를보면우, 막요은약마지지▁looking가 됩니다.0,그리고.b10에서 15 사이의 범위입니다.우리는 그것을 매핑하고 싶습니다.A ~ (65) ~1987F(70), 이는 55를 추가하는 것을 의미합니다.'A'-10).
  6. 숫자에 대한 사례를 보면, 우리는 마지막 합계를 적용하여 매핑하기를 원합니다.b ~ 9에서 0 ~ 9 0(48) ~에게9즉, 57)이 . 즉, -7이 되어야 합니다.'0' - 55).
    이제 7을 곱하면 됩니다.인 것으로 표현되기 에 하만 -1은 이모비 1때문에대사수있용할다습니신지든트가기▁instead다▁but▁use있▁can를 사용할 수 있습니다.& -7 이래(0 & -7) == 0그리고.(-1 & -7) == -7.

추가 고려 사항:

  • 두 루프 하여 색화하위번루변사않다았니습용지하수로 색인화하지 c측정 결과에서 그것을 계산하는 것이 보여지기 때문에.i더 저렴합니다.
  • 정확하게 하게사용을 합니다.i < bytes.Length하면 J를기때허 에서 경계 할 수 .bytes[i]그래서 저는 그 변종을 선택했습니다.
  • bint는 바이트 간의 불필요한 변환을 허용합니다.

이 다보더많보다 더 다음는원우경하을.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)에 따라 성능이 거의 같거나 이 변형보다 약간 느렸습니다.


더 성능을 는 그고더높성위해을능은리,위해▁and▁its▁evenance,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;
}

비트 변환기를 사용할 수 있습니다.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

추가 정보:비트 컨버터.ToString 메서드(바이트[])

저는 오늘 똑같은 문제에 직면했고, 다음 코드를 발견했습니다.

private static string ByteArrayToHex(byte[] barray)
{
    char[] c = new char[barray.Length * 2];
    byte b;
    for (int i = 0; i < barray.Length; ++i)
    {
        b = ((byte)(barray[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(barray[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}

Source: Forum post byte[] Array to Hex String (PZahra의 게시물 참조).0x 접두사를 제거하기 위해 코드를 조금 수정했습니다.

코드에 대한 성능 테스트를 몇 번 해봤는데 비트 컨버터를 사용하는 것보다 거의 8배 더 빨랐습니다.ToString()(패트리지의 게시물에 따라 가장 빠름).

.NET 5 RC2 기준으로 다음을 사용할 수 있습니다.

스팬 매개 변수를 사용하는 오버로드를 사용할 수 있습니다.

이것은 Tomalak의 매우 인기 있는 답변( 이후 편집)의 개정 4에 대한 답변입니다.

저는 이 편집이 틀렸다고 주장하고, 왜 그것이 되돌릴 수 있는지 설명하겠습니다.그 과정에서, 여러분은 몇몇 내부자들에 대해 배울 수 있을 것이고, 조기 최적화가 무엇인지 그리고 어떻게 여러분을 괴롭힐 수 있는지에 대한 또 다른 예를 볼 수 있을 것입니다.

tl;dr: 그냥 사용하세요.Convert.ToByte그리고.String.Substring만약 당신이 급하다면, 그것은 당신이 재구현을 원하지 않는다면 최고의 조합입니다.Convert.ToByte사하지 다않사정용보급고는참조(다른 답변 하지 않는 합니다.Convert.ToByte성능이 필요한 경우.다음 이외의 다른 것은 사용하지 마십시오.String.Substring Convert.ToByte누군가가 이 답변의 댓글에서 이것에 대해 흥미로운 말을 하지 않는 한.

경고:이 대답은 다음과 같은 경우 쓸모없게 될 수 있습니다.Convert.ToByte(char[], Int32)오버로드가 프레임워크에서 구현되었습니다.이것은 곧 일어날 것 같지 않습니다.

일반적으로, 저는 "조숙하게 최적화하지 마세요"라고 말하는 것을 별로 좋아하지 않습니다. 왜냐하면 아무도 "조숙"이 언제인지 모르기 때문입니다.최적화 여부를 결정할 때 고려해야 할 사항은 "최적화 접근 방식을 제대로 조사할 시간과 리소스가 있습니까?"입니다.그렇지 않다면 프로젝트가 더 성숙해질 때까지 또는 성능이 필요할 때까지 기다리십시오(정말 필요한 경우 시간을 낼 수 있습니다).그 사이에, 가능한 한 가장 간단한 것을 대신하십시오.

원본 코드:

    public static byte[] HexadecimalStringToByteArray_Original(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        for (var i = 0; i < outputLength; i++)
            output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
        return output;
    }

개정 4:

    public static byte[] HexadecimalStringToByteArray_Rev4(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
                output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
        }
        return output;
    }

수정사항은 다음을 피합니다.String.Substring를 사용합니다.StringReader 다음과 같습니다주어진 이유는 다음과 같습니다.

편집: 다음과 같은 단일 패스 파서를 사용하여 긴 문자열의 성능을 향상시킬 수 있습니다.

대한 참조 코드를 보면, 이미 "단일 패스"입니다. 그리고 왜 그래야 하지 않습니까?대리 쌍이 아닌 바이트 수준에서 작동합니다.

그러나 새 문자열을 할당하지만 전달할 문자열을 할당해야 합니다.Convert.ToByte은 반복할 배열)를 을 루프 를 재사용할 수 .또한 개정판에서 제공하는 솔루션은 반복할 때마다 다른 개체(2문자 배열)를 할당합니다. 이를 방지하기 위해 해당 할당을 루프 외부에 안전하게 배치하고 어레이를 재사용할 수 있습니다.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                numeral[0] = (char)sr.Read();
                numeral[1] = (char)sr.Read();
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

각 16진수numeral두 자리(숫자)를 사용하여 단일 옥텟을 나타냅니다.

그데왜전를화런를▁call화전▁but왜.StringReader.Read 번?두하여 두 두를 한. 그리고 를 두개 수 있습니다. " 번두?" 두오드 호두를여출두하한배문문열 번읽됩요다니청면하도록에자를의자로버번째▁twice. 그리고 호출 수를 두 번 줄일 수 있습니다.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                var read = sr.Read(numeral, 0, 2);
                Debug.Assert(read == 2);
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

인덱스 당내남만값것은인인스덱병(부에게신다니)만 추가된 입니다._pos도 있었을 것입니다j 변수 예), 중복길내(부)_length및)에 입니다._s 가 없는 것입니다. 다시 말해서, 쓸모가 없습니다.

방법이 궁금하다면,Read"codice_1", 코드를 보세요, 그것이 부르는 것은 단지 전화하는 것뿐입니다.String.CopyTo입력 문자열에 있습니다.나머지는 우리가 필요로 하지 않는 가치를 유지하기 위한 장부상의 간접비입니다.

그래서, 문자열 판독기를 이미 제거하고 전화를 겁니다.CopyTo더 단순하고, 더 명확하고, 더 효율적입니다.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0, j = 0; i < outputLength; i++, j += 2)
        {
            input.CopyTo(j, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

당신은 정말 필요합니까?j두 개의 평행한 단계로 증가하는 색인i물론 아닙니다, 그냥 곱셈하세요.i2(컴파일러가 추가로 최적화할 수 있어야 함).

    public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0; i < outputLength; i++)
        {
            input.CopyTo(i * 2, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

현재 솔루션은 어떤 모습입니까?처음과 똑같이, 단지 사용하는 대신에.String.Substring문자열을 할당하고 데이터를 복사하기 위해 16진수 숫자를 복사하는 중간 배열을 사용한 다음 직접 문자열을 할당하고 배열에서 문자열로 데이터를 다시 복사합니다(문자열 생성자에 전달할 때).문자열이 이미 인턴 풀에 있는 경우 두 번째 복사본이 최적화되지 않을 수 있지만, 그러면String.Substring이 경우에도 피할 수 있습니다.

이 사실을 , 여이본다면분러본면▁in.String.Substring한 번,할 수보다 더 의 낮은 의 내부 하고, ▁used▁are▁again▁knowledge▁code▁same▁that▁uses▁it▁you▁the▁fasterlines▁and▁strings▁it▁of▁string▁see▁some▁in▁low▁how▁allocate,-▁internal▁the▁by▁you다▁constructCopyTo통화 오버헤드를 피하기 위해 직접 그 안에 있습니다.

String.Substring

  • 최악의 경우:하나의 빠른 할당, 하나의 빠른 복사.
  • 최상의 경우:할당도 없고 사본도 없습니다.

수동 방식

  • 최악의 경우:두 개의 일반 할당, 한 개의 일반 복사본, 한 개의 빠른 복사본.
  • 최상의 경우:일반 할당 하나, 일반 사본 하나.

결론?(직접 해당 기능을 재구현하고 싶지 않기 때문에) 사용하려면 다음과 같은 이점을 누릴 수 있는 방법이 없는 것 같습니다.String.Substring원을 그리며 주행하고 휠을 다시 장착하는 것이 전부입니다(최적의 재료를 사용하는 경우에만).

로 참: 사를 사용합니다.Convert.ToByte그리고.String.Substring극단적인 성능이 필요하지 않다면 완벽하게 유효한 선택입니다.기억하십시오. 시간과 리소스가 있을 때만 적절한 작동 방식을 조사할 수 있는 대안을 선택하십시오.

만약 그것이 있었다면.Convert.ToByte(char[], Int32)입니다. (입니다.)String).

는 " "피하다", "피하다", "" 등으로 더 나은 성과를 됩니다.String.Substring 한또피를 피합니다.Convert.ToByte(String, Int32)어쨌든 공연이 필요하다면 정말로 해야 할 일입니다.그렇게 하기 위한 모든 다른 접근법을 발견하기 위한 수많은 다른 답들을 보세요.

고지 사항:참조 소스가 최신인지 확인하기 위해 프레임워크의 최신 버전을 컴파일하지 않았습니다.

자, 이 모든 것이 좋고 논리적으로 들리지만, 여러분이 지금까지 해왔다면 분명할 것입니다.하지만 과연 그것이 사실일까요?

Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz
    Cores: 8
    Current Clock Speed: 2600
    Max Clock Speed: 2600
--------------------
Parsing hexadecimal string into an array of bytes
--------------------
HexadecimalStringToByteArray_Original: 7,777.09 average ticks (over 10000 runs), 1.2X
HexadecimalStringToByteArray_BestEffort: 8,550.82 average ticks (over 10000 runs), 1.1X
HexadecimalStringToByteArray_Rev4: 9,218.03 average ticks (over 10000 runs), 1.0X

네!

벤치 프레임워크를 위한 파트리지의 소품, 해킹하기 쉽습니다.사용되는 입력은 다음 SHA-1 해시를 5000회 반복하여 100,000바이트 길이의 문자열을 만드는 것입니다.

209113288F93A9AB8E474EA78D899AFDBB874355

즐거운 시간 보내세요! (하지만 적당히 최적화하세요.)

닷넷 5 업데이트

에서 byte[] 배열) - ("-16진수")string매개 변수:

System.Convert.ToHexString

var myBytes = new byte[100];
var myString = System.Convert.ToHexString(myBytes);

에서 16진수로 하는 stringbyte[]매개 변수:

System.Convert.FromHexString

var myString  = "E10B116E8530A340BCC7B3EAC208487B";
var myBytes = System.Convert.FromHexString(myString);

@CodesInChaos(역방향 메소드)에 의한 답변 보완

public static byte[] HexToByteUsingByteManipulation(string s)
{
    byte[] bytes = new byte[s.Length / 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        int hi = s[i*2] - 65;
        hi = hi + 10 + ((hi >> 31) & 7);

        int lo = s[i*2 + 1] - 65;
        lo = lo + 10 + ((lo >> 31) & 7) & 0x0f;

        bytes[i] = (byte) (lo | hi << 4);
    }
    return bytes;
}

설명:

& 0x0f합니다.

hi = hi + 10 + ((hi >> 31) & 7);다음과 같습니다.

hi = ch-65 + 10 + (((ch-65) >> 31) & 7);

'0'은 '9'와 . '.hi = ch - 65 + 10 + 7;은 어느것이것입니다.hi = ch - 48은 (으) 입니다.0xffffffff & 7).

'A'의 경우..F입니다hi = ch - 65 + 10;은 (으) 입니다.0x00000000 & 7).

''를 빼고 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f를 사용해야 합니다.0을 이용하여& 0x0f.

65는 다음에 대한 코드입니다.'A'

48은 다음에 대한 코드입니다.'0'

7은 사이의 문자 수입니다.'9'그리고.'A'ASCII 테에서블)에서...456789:;<=>?@ABCD...).

이 문제는 룩업 테이블을 사용하여 해결할 수도 있습니다.이 경우 인코더와 디코더 모두에 소량의 정적 메모리가 필요합니다.그러나 이 방법은 빠릅니다.

  • 인코더 테이블 512바이트 또는 1024바이트(대문자와 소문자가 모두 필요한 경우 두 배 크기)
  • 디코더 테이블 256바이트 또는 64KiB(단일 문자 검색 또는 이중 문자 검색)

내 솔루션은 인코딩 테이블에 1024바이트, 디코딩에 256바이트를 사용합니다.

디코딩

private static readonly byte[] LookupTable = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static byte Lookup(char c)
{
  var b = LookupTable[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

public static byte ToByte(char[] chars, int offset)
{
  return (byte)(Lookup(chars[offset]) << 4 | Lookup(chars[offset + 1]));
}

인코딩

private static readonly char[][] LookupTableUpper;
private static readonly char[][] LookupTableLower;

static Hex()
{
  LookupTableLower = new char[256][];
  LookupTableUpper = new char[256][];
  for (var i = 0; i < 256; i++)
  {
    LookupTableLower[i] = i.ToString("x2").ToCharArray();
    LookupTableUpper[i] = i.ToString("X2").ToCharArray();
  }
}

public static char[] ToCharLower(byte[] b, int bOffset)
{
  return LookupTableLower[b[bOffset]];
}

public static char[] ToCharUpper(byte[] b, int bOffset)
{
  return LookupTableUpper[b[bOffset]];
}

비교

StringBuilderToStringFromBytes:   106148
BitConverterToStringFromBytes:     15783
ArrayConvertAllToStringFromBytes:  54290
ByteManipulationToCharArray:        8444
TableBasedToCharArray:              5651 *

이 해결책

메모

디코딩 중 IOException 및 IndexOutOfRangeException이 발생할 수 있습니다(문자 값이 256보다 너무 높은 경우).스트림 또는 어레이를 디코딩/인코딩하는 방법을 구현해야 합니다. 이는 개념 증명에 불과합니다.

바이트 []를 16진수 문자열로 변환 - 벤치마크/성능 분석


업데이트 날짜: 2022-04-17


.NET 5부터는 변환을 사용해야 합니다.HexString(바이트[])로!

using System;
string result = Convert.ToHexString(bytesToConvert);

이 리더보드 및 벤치마크 정보

Tymine과의 비교는 특히 .NET 5 이후에 구식이고 불완전한 것 같습니다.Convert.ToHexString그래서 저는 ~~16진수 문자열 토끼 구멍에 바이트로 들어가기로 결정했습니다~ 이 두 질문대한 답에서 더 많은 방법으로 새롭고 업데이트된 비교를 만들기로 했습니다.

저는 맞춤형 벤치마킹 스크립트 대신 BenchamrkDotNet을 사용하여 결과를 보다 정확하게 만들 수 있기를 바랍니다.
마이크로 벤치마크는 결코 실제 상황을 나타내지 않을 것이며, 당신은 당신의 테스트를 해야 한다는 것을 기억하세요.

2133MHz에서 2x8GB DDR4가 장착된 AMD Ryzen 5800H커널 5.15.32가 장착Linux에서 이러한 벤치마크를 실행했습니다.
전체 벤치마크를 완료하는 데는 많은 시간이 걸릴 수 있습니다(내 컴퓨터에서는 약 40분).

대문자(대문자) 대 소문자 출력

언급된 모든 방법(별도로 명시되지 않은 경우)은 대문자 출력에만 초점을 맞춥니다.즉, 출력이 다음과 같이 표시됩니다.b33f69.

의 입니다.Convert.ToHexString항상 대문자입니다.그래도 다행히도 와 짝을 이룰 때 성능이 크게 저하되지는 않습니다.ToLower() 둘 다unsafe그게 당신의 관심사라면 방법이 더 빠를 것입니다.

문자열을 효율적으로 소문자로 만드는 것은 일부 방법(특히 비트 연산자 매직이 있는 방법)에서는 어려울 수 있지만 대부분 매개 변수를 변경하기에 충분합니다.X2x2또는 매핑에서 대문자에서 소문자로 변경합니다.

리더보드

다음 기준으로 정렬됩니다.Mean N=100참조점은 StringBuilderForEachByte 메서드입니다.

방법(평균은 나노초 단위) 평균 N=10 비율 N=10 평균 N=100 비율 N = 100 평균 N=500 비율 N=500 평균 N=1k 비율 N = 1k 평균 N=10k 비율 N = 10k 평균 N=100k 비율 N = 100k
String Builder 집계 바이트 추가 형식 364.92 1.48 3,680.00 1.74 18,928.33 1.86 38,362.94 1.87 380,994.74 1.72 42,618,861.57 1.62
각 추가 형식에 대한 문자열 작성기 309.59 1.26 3,203.11 1.52 20,775.07 2.04 41,398.07 2.02 426,839.96 1.93 37,220,750.15 1.41
문자열 결합 선택 310.84 1.26 2,765.91 1.31 13,549.12 1.33 28,691.16 1.40 304,163.97 1.38 63,541,601.12 2.41
String Concat 선택 301.34 1.22 2,733.64 1.29 14,449.53 1.42 29,174.83 1.42 307,196.94 1.39 32,877,994.95 1.25
문자열 조인 배열 변환모든. 279.21 1.13 2,608.71 1.23 13,305.96 1.30 27,207.12 1.32 295,589.61 1.34 62,950,871.38 2.39
StringBuilder 집계 바이트 추가 276.18 1.12 2,599.62 1.23 12,788.11 1.25 26,043.54 1.27 255,389.06 1.16 27,664,344.41 1.05
문자열 ConcatArray 변환모든. 244.81 0.99 2,361.08 1.12 11,881.18 1.16 23,709.21 1.15 265,197.33 1.20 56,044,744.44 2.12
각 바이트에 대한 문자열 작성기 246.09 1.00 2,112.77 1.00 10,200.36 1.00 20,540.77 1.00 220,993.95 1.00 26,387,941.13 1.00
사전 할당된 각 바이트에 대한 문자열 작성기 213.85 0.87 1,897.19 0.90 9,340.66 0.92 19,142.27 0.93 204,968.88 0.93 24,902,075.81 0.94
비트 변환기 교체 140.09 0.57 1,207.74 0.57 6,170.46 0.60 12,438.23 0.61 145,022.35 0.66 17,719,082.72 0.67
니블당 조회 수 63.78 0.26 421.75 0.20 1,978.22 0.19 3,957.58 0.19 35,358.21 0.16 4,993,649.91 0.19
조회 및 이동 53.22 0.22 311.56 0.15 1,461.15 0.14 2,924.11 0.14 26,180.11 0.12 3,771,827.62 0.14
속성 조회 중 41.83 0.17 308.59 0.15 1,473.10 0.14 2,925.66 0.14 28,440.28 0.13 5,060,341.10 0.19
조회 및 시프트 알파벳 배열 37.06 0.15 290.96 0.14 1,387.01 0.14 3,087.86 0.15 29,883.54 0.14 5,136,607.61 0.19
바이트 조작 소수점 35.29 0.14 251.69 0.12 1,180.38 0.12 2,347.56 0.11 22,731.55 0.10 4,645,593.05 0.18
바이트 조작16진수 멀티플리 35.45 0.14 235.22 0.11 1,342.50 0.13 2,661.25 0.13 25,810.54 0.12 7,833,116.68 0.30
바이트 조작16진수 증분 36.43 0.15 234.31 0.11 1,345.38 0.13 2,737.89 0.13 26,413.92 0.12 7,820,224.57 0.30
로컬 검색 중 42.03 0.17 223.59 0.11 1,016.93 0.10 1,979.24 0.10 19,360.07 0.09 4,150,234.71 0.16
조회 및 이동 알파벳 범위 30.00 0.12 216.51 0.10 1,020.65 0.10 2,316.99 0.11 22,357.13 0.10 4,580,277.95 0.17
조회 및 이동 알파벳 범위 곱하기 29.04 0.12 207.38 0.10 985.94 0.10 2,259.29 0.11 22,287.12 0.10 4,563,518.13 0.17
바이트당 조회 수 32.45 0.13 205.84 0.10 951.30 0.09 1,906.27 0.09 18,311.03 0.08 3,908,692.66 0.15
바이트당 검색 범위 25.69 0.10 184.29 0.09 863.79 0.08 2,035.55 0.10 19,448.30 0.09 4,086,961.29 0.15
바이트당 검색 범위 27.03 0.11 184.26 0.09 866.03 0.08 2,005.34 0.10 19,760.55 0.09 4,192,457.14 0.16
Lookup32Span안전하지 않은 다이렉트 16.90 0.07 99.20 0.05 436.66 0.04 895.23 0.04 8,266.69 0.04 1,506,058.05 0.06
Lookup32안전하지 않은 직접 16.51 0.07 98.64 0.05 436.49 0.04 878.28 0.04 8,278.18 0.04 1,753,655.67 0.07
16진수 문자열로 변환 19.27 0.08 64.83 0.03 295.15 0.03 585.86 0.03 5,445.73 0.02 1,478,363.32 0.06
16진수 문자열로 변환합니다.아래쪽() 45.66 - 175.16 - 787.86 - 1,516.65 - 13,939.71 - 2,620,046.76 -

결론

»ConvertToHexString의심할 여지 없이 가장 빠르며, 제 관점에서, 그것은 당신이 선택권을 가지고 있다면 항상 사용되어야 합니다.

using System;

string result = Convert.ToHexString(bytesToConvert);

그렇지 않다면 아래에서 가치가 있다고 생각하는 다른 두 가지 방법을 강조하기로 결정했습니다.나는 강조하지 않기로 결정했습니다.unsafe이러한 코드는 안전하지 않을 뿐만 아니라 제가 작업한 대부분의 프로젝트에서 이러한 코드를 허용하지 않기 때문입니다.

가치있는 언급들

는 첫번째는입니다.LookupPerByteSpan.
는 코는의코거동다니일합의드와에 있는 .LookupPerByte 답변에서 CodesInChaos에 의해.이게 가장 빠릅니다.unsafe벤치마크된 메서드입니다.원본과 이 파일의 차이점은 더 짧은 입력(최대 512바이트)에 스택 할당을 사용하는 것입니다.따라서 이러한 입력에서는 이 방법이 약 10% 더 빠르지만 큰 입력에서는 약 5% 더 느립니다.제가 작업하는 대부분의 데이터가 크기보다 짧기 때문에, 저는 이것을 선택했습니다. LookupSpanPerByteSpan 또한 의 코드 입니다.ReadOnlySpan<byte>매핑이 다른 모든 메서드에 비해 너무 큽니다.

private static readonly uint[] Lookup32 = Enumerable.Range(0, 256).Select(i =>
{
    string s = i.ToString("X2");
    return s[0] + ((uint)s[1] << 16);
}).ToArray();

public string ToHexString(byte[] bytes)
{
    var result = bytes.Length * 2 <= 1024
        ? stackalloc char[bytes.Length * 2]
        : 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);
}

는 두번째는입니다.LookupAndShiftAlphabetSpanMultiply먼저, 저는 이것이 저의 창조물이라는 것을 언급하고 싶습니다.하지만 저는 이 방법이 매우 빠를 뿐만 아니라 이해하기도 쉽다고 생각합니다. 속도는 7으로, "C# 7.3 서한변비발다롯서니됩화에생속도는에다비▁the니롯됩▁declared▁comes"로 선언되었습니다. 여기서 선언된 바와 같습니다.ReadOnlySpan<byte>상수 배열 초기화를 반환하는 메서드 -new byte {1, 2, 3, ...}프로그램의 정적 데이터로 컴파일되므로 중복 메모리 할당이 생략됩니다.[소스]

private static ReadOnlySpan<byte> HexAlphabetSpan => new[]
{
    (byte)'0', (byte)'1', (byte)'2', (byte)'3',
    (byte)'4', (byte)'5', (byte)'6', (byte)'7',
    (byte)'8', (byte)'9', (byte)'A', (byte)'B',
    (byte)'C', (byte)'D', (byte)'E', (byte)'F'
};

public static string ToHexString(byte[] bytes)
{
    var res = bytes.Length * 2 <= 1024 ? stackalloc char[bytes.Length * 2] : new char[bytes.Length * 2];

    for (var i = 0; i < bytes.Length; ++i)
    {
        var j = i * 2;
        res[j] = (char)HexAlphabetSpan[bytes[i] >> 4];
        res[j + 1] = (char)HexAlphabetSpan[bytes[i] & 0xF];
    }

    return new string(res);
}

소스 코드

모든 메소드의 소스 코드, 벤치마크 및 이 답변은 여기에서 Gist on my GitHub에서 찾을 수 있습니다.

왜 그것을 복잡하게 만드나요?이것은 Visual Studio 2008에서 간단합니다.

C#:

string hex = BitConverter.ToString(YourByteArray).Replace("-", "");

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

이것은 좋은 게시물건너라.나는 Waleed의 해결책이 좋습니다.패트리지 검사는 안 해봤는데 꽤 빠른 것 같아요.저는 또한 16진수 문자열을 바이트 배열로 변환하는 역방향 프로세스가 필요했습니다. 그래서 저는 Waleed의 솔루션을 뒤집는 것으로 썼습니다.Tomalak의 원래 솔루션보다 더 빠른지는 확실하지 않습니다.다시 말하지만, 저는 patridge의 테스트를 통해서도 역공정을 실행하지 않았습니다.

private byte[] HexStringToByteArray(string hexString)
{
    int hexStringLength = hexString.Length;
    byte[] b = new byte[hexStringLength / 2];
    for (int i = 0; i < hexStringLength; i += 2)
    {
        int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
        int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
        b[i / 2] = Convert.ToByte(topChar + bottomChar);
    }
    return b;
}

안전한 버전:

public static class HexHelper
{
    [System.Diagnostics.Contracts.Pure]
    public static string ToHex(this byte[] value)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        const string hexAlphabet = @"0123456789ABCDEF";

        var chars = new char[checked(value.Length * 2)];
        unchecked
        {
            for (int i = 0; i < value.Length; i++)
            {
                chars[i * 2] = hexAlphabet[value[i] >> 4];
                chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
            }
        }
        return new string(chars);
    }

    [System.Diagnostics.Contracts.Pure]
    public static byte[] FromHex(this string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (value.Length % 2 != 0)
            throw new ArgumentException("Hexadecimal value length must be even.", "value");

        unchecked
        {
            byte[] result = new byte[value.Length / 2];
            for (int i = 0; i < result.Length; i++)
            {
                // 0(48) - 9(57) -> 0 - 9
                // A(65) - F(70) -> 10 - 15
                int b = value[i * 2]; // High 4 bits.
                int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
                b = value[i * 2 + 1]; // Low 4 bits.
                val += (b - '0') + ((('9' - b) >> 31) & -7);
                result[i] = checked((byte)val);
            }
            return result;
        }
    }
}

안전하지 않은 버전 성능을 선호하고 안전하지 않은 것을 두려워하지 않는 사람들을 위한 버전입니다.약 35% 빠른 ToHex 및 10% 빠른 FromHex.

public static class HexUnsafeHelper
{
    [System.Diagnostics.Contracts.Pure]
    public static unsafe string ToHex(this byte[] value)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        const string alphabet = @"0123456789ABCDEF";

        string result = new string(' ', checked(value.Length * 2));
        fixed (char* alphabetPtr = alphabet)
        fixed (char* resultPtr = result)
        {
            char* ptr = resultPtr;
            unchecked
            {
                for (int i = 0; i < value.Length; i++)
                {
                    *ptr++ = *(alphabetPtr + (value[i] >> 4));
                    *ptr++ = *(alphabetPtr + (value[i] & 0xF));
                }
            }
        }
        return result;
    }

    [System.Diagnostics.Contracts.Pure]
    public static unsafe byte[] FromHex(this string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (value.Length % 2 != 0)
            throw new ArgumentException("Hexadecimal value length must be even.", "value");

        unchecked
        {
            byte[] result = new byte[value.Length / 2];
            fixed (char* valuePtr = value)
            {
                char* valPtr = valuePtr;
                for (int i = 0; i < result.Length; i++)
                {
                    // 0(48) - 9(57) -> 0 - 9
                    // A(65) - F(70) -> 10 - 15
                    int b = *valPtr++; // High 4 bits.
                    int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
                    b = *valPtr++; // Low 4 bits.
                    val += (b - '0') + ((('9' - b) >> 31) & -7);
                    result[i] = checked((byte)val);
                }
            }
            return result;
        }
    }
}

BTW 벤치마크 테스트 변환 함수가 잘못 호출될 때마다 알파벳을 초기화하려면 알파벳이 상수(문자열의 경우) 또는 정적 읽기 전용(char[]의 경우)이어야 합니다.그러면 바이트[]를 문자열로 알파벳 기반 변환하는 속도가 바이트 조작 버전만큼 빨라집니다.

그리고 물론 테스트는 릴리스에서 컴파일해야 합니다(최적화 포함). 디버깅 옵션 "Suppress JIT Optimization"이 해제된 상태에서(코드를 디버깅할 수 있어야 하는 경우 "Enable Just My Code"도 마찬가지).

여기서 많은 답변을 덧붙이지는 않았지만, 저는 16진수 문자열 파서의 상당히 최적화된(수용된 것보다 약 4.5배 더 나은) 간단한 구현을 발견했습니다.첫째, 테스트 결과 출력(첫 번째 배치가 구현):

Give me that string:
04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939f

Time to parse 100,000 times: 50.4192 ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

Accepted answer: (StringToByteArray)
Time to parse 100000 times: 233.1264ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

With Mono's implementation:
Time to parse 100000 times: 777.2544ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

With SoapHexBinary:
Time to parse 100000 times: 845.1456ms
Result as base64: BMY/eEJ0DHflRbsLKt6Qs4TxGfarV7aAt6pXWi9Ak58=
BitConverter'd: 04-C6-3F-78-42-74-0C-77-E5-45-BB-0B-2A-DE-90-B3-84-F1-19-F6-AB-5
7-B6-80-B7-AA-57-5A-2F-40-93-9F

Base64 및 'BitConverter'd' 라인은 정확성을 테스트하기 위한 것입니다.두 값은 동일합니다.

구현:

public static byte[] ToByteArrayFromHex(string hexString)
{
  if (hexString.Length % 2 != 0) throw new ArgumentException("String must have an even length");
  var array = new byte[hexString.Length / 2];
  for (int i = 0; i < hexString.Length; i += 2)
  {
    array[i/2] = ByteFromTwoChars(hexString[i], hexString[i + 1]);
  }
  return array;
}

private static byte ByteFromTwoChars(char p, char p_2)
{
  byte ret;
  if (p <= '9' && p >= '0')
  {
    ret = (byte) ((p - '0') << 4);
  }
  else if (p <= 'f' && p >= 'a')
  {
    ret = (byte) ((p - 'a' + 10) << 4);
  }
  else if (p <= 'F' && p >= 'A')
  {
    ret = (byte) ((p - 'A' + 10) << 4);
  } else throw new ArgumentException("Char is not a hex digit: " + p,"p");

  if (p_2 <= '9' && p_2 >= '0')
  {
    ret |= (byte) ((p_2 - '0'));
  }
  else if (p_2 <= 'f' && p_2 >= 'a')
  {
    ret |= (byte) ((p_2 - 'a' + 10));
  }
  else if (p_2 <= 'F' && p_2 >= 'A')
  {
    ret |= (byte) ((p_2 - 'A' + 10));
  } else throw new ArgumentException("Char is not a hex digit: " + p_2, "p_2");

  return ret;
}

저는 몇 가지 시도를 했습니다.unsafe (를 대 문자로 합니다.if다른 방법으로 순서를 정했지만, 이것이 가장 빨랐습니다.

(이것이 절반의 질문에 대한 답이라는 것을 인정합니다.string->byte[] 변환은 충분히 표현되지 않은 반면 byte[]->string angle은 잘 가려진 것 같습니다.따라서, 이 대답은 다음과 같습니다.)

Microsoft의 개발자들은 다음과 같은 간단한 변환을 제공합니다.

public static string ByteArrayToString(byte[] ba) 
{
    // Concatenate the bytes into one long string
    return ba.Aggregate(new StringBuilder(32),
                            (sb, b) => sb.Append(b.ToString("X2"))
                            ).ToString();
}

위의 내용은 깨끗하고 컴팩트하지만 성능 중독자는 열거자를 사용하여 소리를 지릅니다.Tomalak의 원래 답변의 개선된 버전으로 최고의 성능을 얻을 수 있습니다.

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   

   for(int i=0; i < ba.Length; i++)       // <-- Use for loop is faster than foreach   
       hex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat   

   return hex.ToString();   
} 

이것은 제가 지금까지 여기에 올린 모든 루틴 중에서 가장 빠릅니다.내 말만 믿어서는...각 루틴을 성능 테스트하고 CIL 코드를 직접 검사합니다.

Waleed Eissa 코드의 역함수(16진수 문자열에서 바이트 배열로):

    public static byte[] HexToBytes(this string hexString)        
    {
        byte[] b = new byte[hexString.Length / 2];            
        char c;
        for (int i = 0; i < hexString.Length / 2; i++)
        {
            c = hexString[i * 2];
            b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
            c = hexString[i * 2 + 1];
            b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
        }

        return b;
    }

소문자 지원 기능이 있는 Waleed Eissa 기능:

    public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
    {
        byte addByte = 0x37;
        if (toLowerCase) addByte = 0x57;
        char[] c = new char[barray.Length * 2];
        byte b;
        for (int i = 0; i < barray.Length; ++i)
        {
            b = ((byte)(barray[i] >> 4));
            c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
            b = ((byte)(barray[i] & 0xF));
            c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
        }

        return new string(c);
    }

확장 메서드(거부자: 완전히 테스트되지 않은 코드, BTW...):

public static class ByteExtensions
{
    public static string ToHexString(this byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);

        foreach (byte b in ba)
        {
            hex.AppendFormat("{0:x2}", b);
        }
        return hex.ToString();
    }
}

등. Tomalak의 세 가지 솔루션 중 하나를 사용합니다(마지막 하나는 문자열의 확장 메서드).

노인들을 위한 가장 빠른 방법은...포인터가 그립습니다.

    static public byte[] HexStrToByteArray(string str)
    {
        byte[] res = new byte[(str.Length % 2 != 0 ? 0 : str.Length / 2)]; //check and allocate memory
        for (int i = 0, j = 0; j < res.Length; i += 2, j++) //convert loop
            res[j] = (byte)((str[i] % 32 + 9) % 25 * 16 + (str[i + 1] % 32 + 9) % 25);
        return res;
    }

.NET 5는 변환기를 추가했습니다.ToHexString 메서드입니다.

이전 버전의 .NET을 사용하는 사용자용

internal static class ByteArrayExtensions
{
    
    public static string ToHexString(this byte[] bytes, Casing casing = Casing.Upper)
    {
        Span<char> result = stackalloc char[0];
        if (bytes.Length > 16)
        {
            var array = new char[bytes.Length * 2];
            result = array.AsSpan();
        }
        else
        {
            result = stackalloc char[bytes.Length * 2];
        }

        int pos = 0;
        foreach (byte b in bytes)
        {
            ToCharsBuffer(b, result, pos, casing);
            pos += 2;
        }

        return result.ToString();
    }

    private static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
    {
        uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
        uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;

        buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
        buffer[startingIndex] = (char)(packedResult >> 8);
    }
}

public enum Casing : uint
{
    // Output [ '0' .. '9' ] and [ 'A' .. 'F' ].
    Upper = 0,

    // Output [ '0' .. '9' ] and [ 'a' .. 'f' ].
    Lower = 0x2020U,
}

.NET 저장소 https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/System.Private.CoreLib/src/System/Convert.cs 에서 수정됨 https://github.com/dotnet/runtime/blob/v5.0.3/src/libraries/Common/src/System/HexConverter.cs

테스트: 16진수 문자열과 바이트 배열

저는 대부분의 테스트가 바이트 배열을 16진수 문자열로 변환하는 기능에서 수행된다는 것을 알게 되었습니다.그래서 이 게시물에서 저는 다른 측면인 16진수 문자열을 바이트 배열로 변환하는 기능에 초점을 맞출 것입니다.결과에만 관심이 있는 경우 요약 섹션으로 건너뛸 수 있습니다.테스트 코드 파일은 게시물 끝에 제공됩니다.

레이블

수락된 답변(Tomalak 사용) StringToByteArray V1에서 함수의 이름을 지정하거나 V1로 바로 가기를 원합니다. 나머지 함수의 이름은 V2, V3, V4 등과 같습니다.

참여 기능 색인

정확도 테스트

저는 1바이트의 가능한 256개 값을 모두 전달한 다음 출력이 올바른지 확인하여 정확성을 테스트했습니다.결과:

  • V18에서 "00"으로 시작하는 문자열에 문제가 있습니다(로저 스튜어트 주석 참조).그 외에는 모든 테스트를 통과합니다.
  • 16진수 문자열 알파벳 문자가 대문자인 경우: 모든 함수가 성공적으로 전달됨
  • 16진수 문자열 알파벳 문자가 소문자인 경우 V5_1, V5_2, V7, V8, V15, V19 기능이 실패합니다.

참고: V5_3은 (V5_1 및 V5_2 중) 이 문제를 해결합니다.

성능 테스트

저는 스톱워치 수업을 이용한 성능 테스트를 해봤습니다.

  • 긴 문자열에 대한 성능
input length: 10,000,000 bytes
runs: 100
average elapsed time per run:
V1 = 136.4ms
V2 = 104.5ms
V3 = 22.0ms
V4 = 9.9ms
V5_1 = 10.2ms
V5_2 = 9.0ms
V5_3 = 9.3ms
V6 = 18.3ms
V7 = 9.8ms
V8 = 8.8ms
V9 = 10.2ms
V10 = 19.0ms
V11 = 12.2ms
V12 = 27.4ms
V13 = 21.8ms
V14 = 12.0ms
V15 = 14.9ms
V16 = 15.3ms
V17 = 9.5ms
V18 got excluded from this test, because it was very slow when using very long string
V19 = 222.8ms
V20 = 66.0ms
V21 = 15.4ms

V1 average ticks per run: 1363529.4
V2 is more fast than V1 by: 1.3 times (ticks ratio)
V3 is more fast than V1 by: 6.2 times (ticks ratio)
V4 is more fast than V1 by: 13.8 times (ticks ratio)
V5_1 is more fast than V1 by: 13.3 times (ticks ratio)
V5_2 is more fast than V1 by: 15.2 times (ticks ratio)
V5_3 is more fast than V1 by: 14.8 times (ticks ratio)
V6 is more fast than V1 by: 7.4 times (ticks ratio)
V7 is more fast than V1 by: 13.9 times (ticks ratio)
V8 is more fast than V1 by: 15.4 times (ticks ratio)
V9 is more fast than V1 by: 13.4 times (ticks ratio)
V10 is more fast than V1 by: 7.2 times (ticks ratio)
V11 is more fast than V1 by: 11.1 times (ticks ratio)
V12 is more fast than V1 by: 5.0 times (ticks ratio)
V13 is more fast than V1 by: 6.3 times (ticks ratio)
V14 is more fast than V1 by: 11.4 times (ticks ratio)
V15 is more fast than V1 by: 9.2 times (ticks ratio)
V16 is more fast than V1 by: 8.9 times (ticks ratio)
V17 is more fast than V1 by: 14.4 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 2.1 times (ticks ratio)
V21 is more fast than V1 by: 8.9 times (ticks ratio)
  • 긴 문자열에 대한 V18의 성능
V18 took long time at the previous test, 
so let's decrease length for it:  
input length: 1,000,000 bytes
runs: 100
average elapsed time per run: V1 = 14.1ms , V18 = 146.7ms
V1 average ticks per run: 140630.3
V18 is more SLOW than V1 by: 10.4 times (ticks ratio)
  • 짧은 문자열에 대한 성능
input length: 100 byte
runs: 1,000,000
V1 average ticks per run: 14.6
V2 is more fast than V1 by: 1.4 times (ticks ratio)
V3 is more fast than V1 by: 5.9 times (ticks ratio)
V4 is more fast than V1 by: 15.7 times (ticks ratio)
V5_1 is more fast than V1 by: 15.1 times (ticks ratio)
V5_2 is more fast than V1 by: 18.4 times (ticks ratio)
V5_3 is more fast than V1 by: 16.3 times (ticks ratio)
V6 is more fast than V1 by: 5.3 times (ticks ratio)
V7 is more fast than V1 by: 15.7 times (ticks ratio)
V8 is more fast than V1 by: 18.0 times (ticks ratio)
V9 is more fast than V1 by: 15.5 times (ticks ratio)
V10 is more fast than V1 by: 7.8 times (ticks ratio)
V11 is more fast than V1 by: 12.4 times (ticks ratio)
V12 is more fast than V1 by: 5.3 times (ticks ratio)
V13 is more fast than V1 by: 5.2 times (ticks ratio)
V14 is more fast than V1 by: 13.4 times (ticks ratio)
V15 is more fast than V1 by: 9.9 times (ticks ratio)
V16 is more fast than V1 by: 9.2 times (ticks ratio)
V17 is more fast than V1 by: 16.2 times (ticks ratio)
V18 is more fast than V1 by: 1.1 times (ticks ratio)
V19 is more SLOW than V1 by: 1.6 times (ticks ratio)
V20 is more fast than V1 by: 1.9 times (ticks ratio)
V21 is more fast than V1 by: 11.4 times (ticks ratio)

테스트 코드

다음 코드 https://github.com/Ghosticollis/performance-tests/blob/main/MTestPerformance.cs 을 사용하기 전에 이 게시물 아래에 있는 고지 사항 섹션을 읽어보는 것이 좋습니다.

요약

성능이 우수하고 대문자와 소문자를 모두 지원하므로 다음 기능 중 하나를 사용하는 것이 좋습니다.

다음은 V5_3의 최종 모양입니다.

static byte[] HexStringToByteArrayV5_3(string hexString) {
    int hexStringLength = hexString.Length;
    byte[] b = new byte[hexStringLength / 2];
    for (int i = 0; i < hexStringLength; i += 2) {
        int topChar = hexString[i];
        topChar = (topChar > 0x40 ? (topChar & ~0x20) - 0x37 : topChar - 0x30) << 4;
        int bottomChar = hexString[i + 1];
        bottomChar = bottomChar > 0x40 ? (bottomChar & ~0x20) - 0x37 : bottomChar - 0x30;
        b[i / 2] = (byte)(topChar + bottomChar);
    }
    return b;
}

부인

경고:저는 테스트에 대한 적절한 지식이 없습니다.이러한 원시 테스트의 주요 목적은 게시된 모든 기능에서 무엇이 좋을지에 대한 간략한 개요를 제공하는 것입니다.정확한 결과가 필요한 경우 적절한 테스트 도구를 사용하십시오.

마지막으로 스택 오버플로에 적극적으로 참여하는 것이 처음이라 말씀드리고 싶습니다. 제 게시물이 부족하다면 죄송합니다. 이 게시물을 개선하기 위한 의견을 주시면 감사하겠습니다.

SQL 문자열에 삽입하는 경우(명령 매개 변수를 사용하지 않는 경우):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}

속도 면에서, 이것은 여기서 무엇보다 나은 것 같습니다.

  public static string ToHexString(byte[] data) {
    byte b;
    int i, j, k;
    int l = data.Length;
    char[] r = new char[l * 2];
    for (i = 0, j = 0; i < l; ++i) {
      b = data[i];
      k = b >> 4;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
      k = b & 15;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
    }
    return new string(r);
  }

올리프로, 당신이 제안한 코드를 받지 못했습니다. hex[i] + hex[i+1]분명히 답례했습니다.int.

하지만 저는 웨일즈 코드에서 힌트를 얻어 이것을 함께 망치질함으로써 어느 정도 성공을 거두었습니다.그것은 정말 못생겼지만 제 테스트에 따르면 (패트리지 테스트 메커니즘을 사용하여) 다른 것들에 비해 작동하고 3분의 1의 시간에 성능이 발휘되는 것 같습니다.입력 크기에 따라 다릅니다.?:s로 전환하여 0-9를 먼저 분리하면 문자보다 숫자가 많기 때문에 약간 더 빠른 결과를 얻을 수 있습니다.

public static byte[] StringToByteArray2(string hex)
{
    byte[] bytes = new byte[hex.Length/2];
    int bl = bytes.Length;
    for (int i = 0; i < bl; ++i)
    {
        bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
        bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
    }
    return bytes;
}

이 ByteArrayToHexViaByteManulation 버전이 더 빠를 수 있습니다.

내 보고서에서:

  • ByteArrayToHexViaByteManulation3: 평균 눈금 1,68개(1000회 이상 실행), 17,5X
  • ByteArrayToHexViaByteManulation2: 평균 눈금 1,73개(1000회 이상 실행), 16,9X
  • ByteArrayToHexViaByteManulation: 평균 눈금 2,90개(1000회 이상 실행), 10,1X
  • ByteArrayToHexViaLookupAndShift: 평균 눈금 3,22개(1000회 이상 실행), 9,1X
  • ...

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation3(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] = hexAlphabet[b];
            b = ((byte)(bytes[i] & 0xF));
            c[i * 2 + 1] = hexAlphabet[b];
        }
        return new string(c);
    }
    

그리고 저는 이것이 최적화라고 생각합니다.

    static private readonly char[] hexAlphabet = new char[]
        {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    static string ByteArrayToHexViaByteManipulation4(byte[] bytes)
    {
        char[] c = new char[bytes.Length * 2];
        for (int i = 0, ptr = 0; i < bytes.Length; i++, ptr += 2)
        {
            byte b = bytes[i];
            c[ptr] = hexAlphabet[b >> 4];
            c[ptr + 1] = hexAlphabet[b & 0xF];
        }
        return new string(c);
    }

저는 16진수를 디코딩하는 데에도 비트-피들링을 사용하는 답이 있기 때문에 이 비트-피들링 대회에 참가할 것입니다.문자 배열을 사용하는 것이 호출하는 것보다 훨씬 빠를 수 있습니다.StringBuilder방법도 시간이 걸릴 것입니다.

public static String ToHex (byte[] data)
{
    int dataLength = data.Length;
    // pre-create the stringbuilder using the length of the data * 2, precisely enough
    StringBuilder sb = new StringBuilder (dataLength * 2);
    for (int i = 0; i < dataLength; i++) {
        int b = data [i];

        // check using calculation over bits to see if first tuple is a letter
        // isLetter is zero if it is a digit, 1 if it is a letter
        int isLetter = (b >> 7) & ((b >> 6) | (b >> 5)) & 1;

        // calculate the code using a multiplication to make up the difference between
        // a digit character and an alphanumerical character
        int code = '0' + ((b >> 4) & 0xF) + isLetter * ('A' - '9' - 1);
        // now append the result, after casting the code point to a character
        sb.Append ((Char)code);

        // do the same with the lower (less significant) tuple
        isLetter = (b >> 3) & ((b >> 2) | (b >> 1)) & 1;
        code = '0' + (b & 0xF) + isLetter * ('A' - '9' - 1);
        sb.Append ((Char)code);
    }
    return sb.ToString ();
}

public static byte[] FromHex (String hex)
{

    // pre-create the array
    int resultLength = hex.Length / 2;
    byte[] result = new byte[resultLength];
    // set validity = 0 (0 = valid, anything else is not valid)
    int validity = 0;
    int c, isLetter, value, validDigitStruct, validDigit, validLetterStruct, validLetter;
    for (int i = 0, hexOffset = 0; i < resultLength; i++, hexOffset += 2) {
        c = hex [hexOffset];

        // check using calculation over bits to see if first char is a letter
        // isLetter is zero if it is a digit, 1 if it is a letter (upper & lowercase)
        isLetter = (c >> 6) & 1;

        // calculate the tuple value using a multiplication to make up the difference between
        // a digit character and an alphanumerical character
        // minus 1 for the fact that the letters are not zero based
        value = ((c & 0xF) + isLetter * (-1 + 10)) << 4;

        // check validity of all the other bits
        validity |= c >> 7; // changed to >>, maybe not OK, use UInt?

        validDigitStruct = (c & 0x30) ^ 0x30;
        validDigit = ((c & 0x8) >> 3) * (c & 0x6);
        validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);

        validLetterStruct = c & 0x18;
        validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
        validity |= isLetter * (validLetterStruct | validLetter);

        // do the same with the lower (less significant) tuple
        c = hex [hexOffset + 1];
        isLetter = (c >> 6) & 1;
        value ^= (c & 0xF) + isLetter * (-1 + 10);
        result [i] = (byte)value;

        // check validity of all the other bits
        validity |= c >> 7; // changed to >>, maybe not OK, use UInt?

        validDigitStruct = (c & 0x30) ^ 0x30;
        validDigit = ((c & 0x8) >> 3) * (c & 0x6);
        validity |= (isLetter ^ 1) * (validDigitStruct | validDigit);

        validLetterStruct = c & 0x18;
        validLetter = (((c - 1) & 0x4) >> 2) * ((c - 1) & 0x2);
        validity |= isLetter * (validLetterStruct | validLetter);
    }

    if (validity != 0) {
        throw new ArgumentException ("Hexadecimal encoding incorrect for input " + hex);
    }

    return result;
}

Java 코드에서 변환되었습니다.

성능을 위해 저는 drphrozens 솔루션을 선택할 것입니다.디코더에 대한 작은 최적화는 "<4"를 제거하기 위해 두 문자 모두에 대한 테이블을 사용하는 것일 수 있습니다.

분명히 두 가지 메소드 호출은 비용이 많이 듭니다. 또는 데이터기타 일 수 에 대해 , " " " " " " " (CRC, " " " " " " " " " " " " " " " (CRC, " " " " " " " " " " " " " " " 은 " " " " " " 은 " " " " " 은 " " " " " " " " " " 은 " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "if (b == 255)...건너뛸 수 있으므로 메서드가 모두 호출할 수도 있습니다.

용사를 합니다.offset++그리고.offsetoffset그리고.offset + 1이론적으로 도움이 될 수도 있지만 컴파일러가 저보다 더 잘 처리하는 것 같습니다.

private static readonly byte[] LookupTableLow = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static readonly byte[] LookupTableHigh = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static byte LookupLow(char c)
{
  var b = LookupTableLow[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

private static byte LookupHigh(char c)
{
  var b = LookupTableHigh[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

public static byte ToByte(char[] chars, int offset)
{
  return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}

이것은 제 머리에서 나온 것이며 테스트되거나 벤치마킹되지 않았습니다.

언급URL : https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa

반응형