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


1371

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


8
아래의 허용 된 대답은 문자열에서 바이트로 변환에 끔찍한 문자열을 할당하는 것으로 보입니다. 이것이 성능에 어떤 영향을 미치는지 궁금합니다
Wim Coenen

9
SoapHexBinary 클래스는 내가 원하는 것을 정확하게 수행합니다.
Mykroft

1 개의 게시물에 2 개의 질문을하는 것이 표준이 아닌 것 같습니다.
SandRock

답변:


1353

어느 한 쪽:

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.


24
SubString을 사용하고 있습니다. 이 루프는 끔찍한 양의 문자열 객체를 할당하지 않습니까?
Wim Coenen

30
솔직히-성능이 크게 떨어질 때까지 이것을 무시하고 런타임과 GC를 처리하는 것을 신뢰하는 경향이 있습니다.
Tomalak

87
바이트는 두 개의 니블이므로 바이트 배열을 유효하게 나타내는 16 진 문자열은 짝수 문자 수를 가져야합니다. 0은 어디에도 추가해서는 안됩니다. 1을 추가하려면 잠재적으로 위험한 무효 데이터에 대해 가정합니다. 16 진수 문자열에 홀수 개의 문자가 포함되어 있으면 StringToByteArray 메서드는 FormatException을 발생시킵니다.
David Boike

7
@ 00jt F == 0F라고 가정해야합니다. 0F와 같거나 입력이 잘리고 F는 실제로 수신하지 않은 것의 시작입니다. 이러한 가정을 만드는 것은 귀하의 상황에 달려 있지만, 범용 함수는 호출 코드에 대한 가정을하는 대신 홀수 문자를 유효하지 않은 것으로 거부해야한다고 생각합니다.
David Boike

11
@DavidBoike이 질문은 "잘려진 스트림 값을 처리하는 방법"과 관련이 없습니다. 그것은 문자열에 관한 것입니다. 문자열 myValue = 10.ToString ( "X"); myValue는 "0A"가 아닌 "A"입니다. 이제 해당 문자열을 바이트로 다시 읽습니다. 죄송합니다.
00jt

488

성능 분석

참고 : 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변형을 추가 했습니다 . 안전하지 않은 게임에서 플레이하려면 짧은 문자열과 큰 텍스트 모두에서 이전의 최고 우승자에 비해 큰 성능 향상을 얻을 수 있습니다.


Waleed의 답변에서 코드를 테스트 하시겠습니까? 매우 빠른 것 같습니다. stackoverflow.com/questions/311165/…
Cristian Diaconescu

5
직접 요청한 작업을 수행 할 수있는 코드를 만들었음에도 불구하고 Waleed 답변을 포함하도록 테스트 코드를 업데이트했습니다. 모든 심술은 제쳐두고 훨씬 빠릅니다.
patridge

2
@CodesInChaos 완료. 그리고 그것은 내 테스트에서도 상당히 이겼습니다. 나는 최고의 방법 중 하나를 완전히 이해하는 척하지 않지만 직접적인 상호 작용에서 쉽게 숨겨져 있습니다.
patridge

6
이 답변은 "자연적"이거나 평범한 것에 대한 질문에 답할 의도가 없습니다. 목표는 사람들에게 기본적인 성능 벤치 마크를 제공하는 것입니다. 이러한 변환을 수행해야 할 때는 많은 성능을 발휘하는 경향이 있기 때문입니다. 누군가 원시 속도가 필요한 경우 원하는 컴퓨팅 환경에서 적절한 테스트 데이터로 벤치 마크를 실행하면됩니다. 그런 다음 해당 메소드를 확장 메소드에 집어 넣어 다시 구현을 보지 마십시오 (예 :) bytes.ToHexStringAtLudicrousSpeed().
patridge

2
고성능 룩업 테이블 기반 구현을 방금 생성했습니다. 안전한 변종은 내 CPU의 현재 리더보다 약 30 % 빠릅니다. 안전하지 않은 변종은 훨씬 빠릅니다. stackoverflow.com/a/24343727/445517
코드 InChaos

244

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

35
SoapHexBinary는 .NET 1.0에서 사용할 수 있으며 mscorlib에 있습니다. 재미있는 네임 스페이스 임에도 불구하고 질문과 정확히 일치합니다.
교활한 그리폰

4
좋은 발견! 다른 솔루션과 마찬가지로 GetStringToBytes에 홀수 문자열을 앞에 0으로 채워야합니다.
카터 메 드린

구현 생각을 보셨습니까? 허용 된 답변에는 IMHO가 더 좋습니다.
mfloryan

6
여기에 모노 구현이 흥미 롭습니다
Jeremy

1
SoapHexBinary는 .NET Core / .NET 표준에서 지원되지 않습니다 ...
juFo

141

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

꽤 빠릅니다.

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정수를 만들면 바이트를 불필요하게 변환 할 수 있습니다.

10
그리고 hex stringbyte[] array?
AaA

15
흑 마법을 사용하여 소스를 올바르게 인용 한 +1 우박 크 툴후.
Edward

4
string to byte []는 어떻습니까?
Syaiful Nizam Yahya

9
좋은! 소문자 출력이 필요한 사람들을 위해 표현은 분명히87 + b + (((b-10)>>31)&-39)
eXavier

2
@AaA " byte[] array"는 문자 그대로 바이트 배열의 배열을 의미합니다 byte[][]. 또는 . 나는 단지 재미를 파고 있었다.
CoolOppo

97

보다 유연 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")));

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


21
더 짧은 : String.Concat (Array.ConvertAll (bytes, x => x.ToString ( "X2"))
Nestor

14
더 짧은 : String.Concat (bytes.Select (b => b.ToString ( "X2"))) [.NET4]
Allon Guralnek

14
질문의 절반 만 답합니다.
교활한 그리폰

1
두 번째에 .Net 4가 필요한 이유는 무엇입니까? String.Concat은 .Net 2.0에 있습니다.
Polyfun

2
이러한 "90 's 스타일"루프는 일반적으로 더 빠르지 만 무시할만한 양으로 대부분의 상황에서 중요하지 않습니다. 그래도 언급 할 가치가 있습니다
Austin_Anderson

69

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

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

안전하지 않은 버전에서 룩업 테이블을 생성하면 미리 계산 된 바이트의 니블을 교체하는 이유는 무엇입니까? 엔디안은 여러 바이트로 구성된 엔터티의 순서 만 변경한다고 생각했습니다.
Raif Atef

@RaifAtef 여기서 중요한 것은 니블의 순서가 아닙니다. 그러나 32 비트 정수에서 16 비트 단어의 순서. 그러나 엔디안과 상관없이 동일한 코드를 실행할 수 있도록 다시 작성하는 것을 고려하고 있습니다.
코드 InChaos

코드를 다시 읽으면 나중에 char *를 uint *에 캐스팅하고 할당 할 때 (16 진수 char를 생성 할 때) 런타임 / CPU가 바이트를 뒤집기 때문에이 작업을 수행했다고 생각합니다 (uint가 처리되지 않기 때문에) 2 개의 16 비트 문자와 동일하므로) 보상을 위해 사전에 충돌하고 있습니다. 내가 맞아? 엔디안은 혼란 스럽습니다 :-).
Raif Atef

4
이것은 질문의 절반에 답합니다. 16 진수 문자열에서 바이트까지는 어떻습니까?
Narvalex

3
@CodesInChaos Span대신에 지금 사용할 수 있는지 궁금합니다 unsafe??
Konrad


56

오늘 막 똑같은 문제가 발생 하여이 코드를 발견했습니다.

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

출처 : Forum post byte [] 배열을 16 진 문자열로 (PZahra의 게시물 참조). 0x 접두사를 제거하기 위해 코드를 약간 수정했습니다.

코드에 대한 성능 테스트를 수행했으며 BitConverter.ToString () (패트리 지 포스트에 따르면 가장 빠름)을 사용하는 것보다 거의 8 배 빠릅니다.


이것은 가장 적은 메모리를 사용한다는 것은 말할 것도 없습니다. 중간 문자열이 생성되지 않았습니다.
Chochos

8
질문의 절반 만 답합니다.
교활한 그리폰

NETMF를 포함하여 기본적으로 모든 버전의 NET에서 작동하기 때문에 좋습니다. 승자!
Jonesome Reinstate Monica

1
허용되는 답변은 질문의 다른 절반을 나타내는 두 가지 우수한 HexToByteArray 메서드를 제공합니다. Waleed의 솔루션은 프로세스에서 많은 수의 문자열을 만들지 않고이를 수행하는 방법에 대한 실행중인 질문에 대답합니다.
Brendten Eickstaedt

새로운 문자열 (c)가 복사하고 다시 할당합니까, 아니면 단순히 char []를 감쌀 수있는 시점을 알기에 충분히 똑똑합니까?
jjxtra

19

이것은 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. 주어진 이유는 다음과 같습니다.

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

에 대한 참조 코드를String.Substring 보면 이미 "단일 패스"입니다. 왜 안되나요? 대리 쌍이 아닌 바이트 수준에서 작동합니다.

그러나 새 문자열을 할당하지만 Convert.ToByte어쨌든 전달할 문자열을 할당해야 합니다. 또한 개정판에 제공된 솔루션은 모든 반복 (두 문자 배열)에 또 다른 객체를 할당합니다. 해당 할당을 루프 외부에 안전하게 넣고 배열을 재사용하여이를 피할 수 있습니다.

    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는 두 자리 (기호)를 사용하는 단일 옥텟을 나타냅니다.

그런데 왜 StringReader.Read두 번 전화 해야합니까? 두 번째 오버로드를 호출하여 두 문자 배열에서 두 문자를 한 번에 읽도록 ​​요청하십시오. 통화량을 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++)
            {
                var read = sr.Read(numeral, 0, 2);
                Debug.Assert(read == 2);
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

남은 것은 "value"만 추가 한 병렬 색인 (internal ), 중복 길이 변수 (internal ) 및 입력에 대한 중복 참조를 _pos선언 할 수 있는 병렬 색인 (internal )입니다. 문자열 (내부 ). 다시 말해, 쓸모가 없습니다.j_length_s

Read"읽는" 방법이 궁금하다면 코드를 보면 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합니까? 물론 i두 개만 곱하면 안됩니다 (컴파일러가 추가에 최적화 할 수 있어야 함).

    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될 수 있지만 이러한 경우에는이를 피할 수 있습니다.

사실, String.Substring다시 살펴보면 , 문자열을 평소보다 빨리 할당하기 위해 문자열을 구성하는 방법에 대한 저수준 내부 지식을 사용하고 CopyTo피하기 위해 직접 사용하는 동일한 코드를 인라인합니다 통화 오버 헤드.

String.Substring

  • 최악의 경우 : 하나의 빠른 할당, 하나의 빠른 사본.
  • 최선의 경우 : 할당 없음, 사본 없음.

수동 방법

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

결론? (기능을 직접 구현하고 싶지 않기 때문에) 사용Convert.ToByte(String, Int32) 하고 싶다면 이길 방법이없는 것 같습니다 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

예!

벤치 프레임 워크를 위해 Partridge에 소품을 올리면 쉽게 해킹 할 수 있습니다. 사용 된 입력은 100,000 바이트 길이의 문자열을 만들기 위해 5000 번 반복 된 다음 SHA-1 해시입니다.

209113288F93A9AB8E474EA78D899AFDBB874355

즐기세요! (그러나 적당량으로 최적화하십시오.)


error : { "인식 할 수있는 숫자를 찾을 수 없습니다."}
Priya Jagtap

17

@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).

'a'.. 'f'의 경우 큰 숫자를 사용해야하므로 0를 사용하여 비트 를 만들어 기본 버전에서 32를 빼야합니다 & 0x0f.

65는 코드입니다 'A'

48은 '0'

(7) 사이의 문자의 수 '9''A'아스키 테이블에 ( ...456789:;<=>?@ABCD...).


16

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

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

내 솔루션은 인코딩 테이블에 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보다 큰 경우). 스트림 또는 배열을 디코딩 / 인코딩하는 방법을 구현해야합니다. 이는 개념에 대한 증거 일뿐입니다.


2
CLR에서 코드를 실행할 때 256 바이트의 메모리 사용량은 무시할 수 있습니다.
고인돌

9

이것은 좋은 게시물입니다. Waleed의 솔루션이 마음에 듭니다. patridge의 테스트를 통해 실행하지는 않았지만 꽤 빠릅니다. 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;
}

이 코드는 16 진 문자열이 대문자 알파 문자를 사용한다고 가정하고 16 진 문자열이 소문자 알파를 사용하면 폭발합니다. 안전을 위해 입력 문자열에서 "대문자"변환을 수행하려고 할 수 있습니다.
Marc Novakowski

그것은 마크 관측치입니다. 이 코드는 Waleed의 솔루션을 뒤집기 위해 작성되었습니다. ToUpper 호출은 알고리즘을 약간 느리게하지만 소문자 알파 문자를 처리 할 수있게합니다.
Chris F

3
Convert.ToByte (topChar + bottomChar)는 (바이트) (+ topChar bottomChar)로 기록 될 수있다
아미르 라자

큰 성능 저하없이 두 경우 모두 처리하기 위해hexString[i] &= ~0x20;
Ben Voigt

9

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

씨#:

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

VB :

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

2
고성능 솔루션이 필요한 성능 때문입니다. :)
Ricky

7

여기에 많은 답변을 쌓아 두지는 않지만 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이 잘 커버되는 것처럼 느꼈습니다. 따라서이 답변.)


1
크 누스의 추종자 : 몇 분마다 수천 헥스 문자열을 파싱해야하기 때문에이 작업을 수행 했으므로 가능한 한 빨리 (내부 루프에서) 빠르도록하는 것이 중요합니다. 그러한 파싱이 많이 발생하지 않으면 Tomalak의 솔루션이 현저하게 느려지지 않습니다.
벤 모셔

5

안전한 버전 :

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

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

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 호출 된 변환 함수가 잘못 될 때마다 알파벳을 초기화하는 벤치 마크 테스트의 경우 알파벳은 const (문자열) 또는 정적 읽기 전용 (char [])이어야합니다. 그런 다음 byte []를 문자열로 알파벳 기반으로 변환하면 바이트 조작 버전만큼 빨라집니다.

물론 테스트는 릴리스에서 (최적화로) 컴파일되고 디버그 옵션 "Suppress JIT 최적화"가 해제되어 있어야합니다 (코드를 디버깅 할 수 있어야하는 경우 "내 코드 만 활성화"와 동일).


5

Waleed Eissa 코드에 대한 역함수 (16 진 배열을 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);
    }

4

확장 방법 (면책 조항 : 완전히 테스트되지 않은 코드, 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의 세 가지 솔루션 중 하나를 사용하십시오 (마지막에서 확장 방법 인 마지막 솔루션 ).


이와 같은 질문에 대해 코드를 제공하기 전에 코드를 테스트해야합니다.
jww

3

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 코드를 직접 검사하십시오.


2
반복자는이 코드의 주요 문제가 아닙니다. 벤치마킹해야합니다 b.ToSting("X2").
고인돌

2

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

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

Source == null또는 Source.Length == 0우리가 문제가 있다면 !
Andrei Krasutski

2

속도면에서 이것은 여기의 어떤 것보다 낫습니다.

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

2

귀하가 제안한 코드를받지 못했습니다, Olipro. hex[i] + hex[i+1]분명히을 반환했습니다 int.

그러나 Waleeds 코드에서 힌트를 얻고 이것을 함께 망치는 것으로 성공했습니다. 그것은 못생긴 것처럼 보이지만 내 테스트 (패트리 지 테스트 메커니즘 사용)에 따라 다른 것보다 1/3의 시간에 작동하고 수행하는 것 같습니다. 입력 크기에 따라 다릅니다. 0 : 9를 먼저 분리하기 위해? : s 주위를 전환하면 문자보다 많은 숫자가 있기 때문에 약간 더 빠른 결과를 얻을 수 있습니다.

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

2

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

내 보고서에서 :

  • ByteArrayToHexViaByteManipulation3 : 1,68 평균 틱 (1000 회 이상), 17,5X
  • ByteArrayToHexViaByteManipulation2 : 1,73 개의 평균 틱 (1000 회 이상), 16,9X
  • ByteArrayToHexViaByteManipulation : 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);
    }

2

비트-피들 링 (bit-fiddling)을 사용하여 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 코드에서 변환되었습니다.


흠, 나는 이것을 이것을 int 대신에 최적화 Char[]하고 Char내부적으로 사용해야한다 .
Maarten Bodewes

C #의 경우 루프 외부가 아닌 변수가 사용되는 위치를 초기화하는 것이 컴파일러에서 최적화하도록하는 것이 좋습니다. 어느 쪽이든 동등한 성능을 얻습니다.
Peteter 2016 년

2

성능을 위해 drphrozens 솔루션을 사용합니다. 디코더를위한 작은 최적화는 "<< 4"를 제거하기 위해 char에 대한 테이블을 사용하는 것입니다.

분명히 두 개의 메소드 호출은 비용이 많이 듭니다. 입력 또는 출력 데이터 (CRC, 체크섬 등)에서 어떤 종류의 검사가 수행되는 if (b == 255)...경우 건너 뛸 수 있으며 또한 메서드 호출이 모두 호출 될 수 있습니다.

사용 offset++하고 offset대신 offset하고 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]));
}

이것은 내 머리 꼭대기에 있으며 테스트되거나 벤치마킹되지 않았습니다.


1

다양성에 대한 또 다른 변형 :

public static byte[] FromHexString(string src)
{
    if (String.IsNullOrEmpty(src))
        return null;

    int index = src.Length;
    int sz = index / 2;
    if (sz <= 0)
        return null;

    byte[] rc = new byte[sz];

    while (--sz >= 0)
    {
        char lo = src[--index];
        char hi = src[--index];

        rc[sz] = (byte)(
            (
                (hi >= '0' && hi <= '9') ? hi - '0' :
                (hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
                (hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
                0
            )
            << 4 | 
            (
                (lo >= '0' && lo <= '9') ? lo - '0' :
                (lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
                (lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
                0
            )
        );
    }

    return rc;          
}

1

속도에 최적화되어 있지 않지만 대부분의 답변 (.NET 4.0)보다 LINQy가 더 많습니다.

<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
    hex = If(hex, String.Empty)
    If hex.Length Mod 2 = 1 Then hex = "0" & hex
    Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function

<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
    Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function

1

두 개의 니블 연산을 하나로 접는 두 개의 매시업.

아마도 매우 효율적인 버전입니다.

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

linc-with-bit-hacking 버전 :

public static string ByteArrayToString(byte[] ba)
{
    return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}

그리고 반대로 :

public static byte[] HexStringToByteArray( string s )
{
    byte[] ab = new byte[s.Length>>1];
    for( int i = 0; i < s.Length; i++ )
    {
        int b = s[i];
        b = (b - '0') + ((('9' - b)>>31)&-7);
        ab[i>>1] |= (byte)(b << 4*((i&1)^1));
    }
    return ab;
}

1
HexStringToByteArray ( "09")는 나쁘다는 0x02 반환
CoperNick

1

또 다른 방법은 stackallocGC 메모리 압력을 낮추기 위해 사용 하는 것입니다 .

static string ByteToHexBitFiddle(byte[] bytes)
{
        var c = stackalloc char[bytes.Length * 2 + 1];
        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));
        }
        c[bytes.Length * 2 ] = '\0';
        return new string(c);
}

1

여기 내 총이 있습니다. 문자열과 바이트를 확장하기 위해 확장 클래스 쌍을 만들었습니다. 큰 파일 테스트에서 성능은 바이트 조작 2와 비슷합니다.

ToHexString에 대한 아래 코드는 조회 및 이동 알고리즘의 최적화 된 구현입니다. 그것은 Behrooz와 거의 동일하지만 foreach반복을 사용하고 카운터가 명시 적으로 인덱싱하는 것보다 빠릅니다 for.

내 컴퓨터에서 Byte Manipulation 2 뒤에 2 위를 차지하며 매우 읽을 수있는 코드입니다. 다음 테스트 결과도 관심이 있습니다.

ToHexStringCharArrayWithCharArrayLookup : 41,589.69 평균 틱 (1000 회 이상), 1.5X ToHexStringCharArrayWithStringLookup : 50,764.06 평균 틱 (1000 회 이상), 1.2X ToHexStringStringBuilderWithCharArrayLookup : 62,812.87 평균 틱 (1000 회 이상), 1.0X

위의 결과를 바탕으로 결론을 내리는 것이 안전 해 보입니다.

  1. 조회와 문자 배열을 수행하기 위해 문자열로 인덱싱하는 것에 대한 처벌은 큰 파일 테스트에서 중요합니다.
  2. 알려진 용량의 StringBuilder와 알려진 크기의 char 배열을 사용하여 문자열을 만드는 데 대한 처벌은 훨씬 더 중요합니다.

코드는 다음과 같습니다.

using System;

namespace ConversionExtensions
{
    public static class ByteArrayExtensions
    {
        private readonly static char[] digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

        public static string ToHexString(this byte[] bytes)
        {
            char[] hex = new char[bytes.Length * 2];
            int index = 0;

            foreach (byte b in bytes)
            {
                hex[index++] = digits[b >> 4];
                hex[index++] = digits[b & 0x0F];
            }

            return new string(hex);
        }
    }
}


using System;
using System.IO;

namespace ConversionExtensions
{
    public static class StringExtensions
    {
        public static byte[] ToBytes(this string hexString)
        {
            if (!string.IsNullOrEmpty(hexString) && hexString.Length % 2 != 0)
            {
                throw new FormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");
            }

            hexString = hexString.ToUpperInvariant();
            byte[] data = new byte[hexString.Length / 2];

            for (int index = 0; index < hexString.Length; index += 2)
            {
                int highDigitValue = hexString[index] <= '9' ? hexString[index] - '0' : hexString[index] - 'A' + 10;
                int lowDigitValue = hexString[index + 1] <= '9' ? hexString[index + 1] - '0' : hexString[index + 1] - 'A' + 10;

                if (highDigitValue < 0 || lowDigitValue < 0 || highDigitValue > 15 || lowDigitValue > 15)
                {
                    throw new FormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");
                }
                else
                {
                    byte value = (byte)((highDigitValue << 4) | (lowDigitValue & 0x0F));
                    data[index / 2] = value;
                }
            }

            return data;
        }
    }
}

다음은 내 컴퓨터의 @patridge 테스트 프로젝트에 코드를 넣을 때 얻은 테스트 결과입니다. 또한 16 진수에서 바이트 배열로 변환하기위한 테스트를 추가했습니다. 내 코드를 사용한 테스트 실행은 ByteArrayToHexViaOptimizedLookupAndShift 및 HexToByteArrayViaByteManipulation입니다. HexToByteArrayViaConvertToByte는 XXXX에서 가져 왔습니다. HexToByteArrayViaSoapHexBinary는 @Mykroft의 답변 중 하나입니다.

인텔 펜티엄 III 제온 프로세서

    Cores: 4 <br/>
    Current Clock Speed: 1576 <br/>
    Max Clock Speed: 3092 <br/>

바이트 배열을 16 진수 문자열 표현으로 변환


ByteArrayToHexViaByteManipulation2 : 39,366.64 평균 틱 (1000 회 이상), 22.4X

ByteArrayToHexViaOptimizedLookupAndShift : 41,588.64 평균 틱 (1000 회 이상), 21.2X

ByteArrayToHexViaLookup : 55,509.56 평균 틱 (1000 회 이상), 15.9X

ByteArrayToHexViaByteManipulation : 65,349.12 평균 틱 (1000 회 이상), 13.5X

ByteArrayToHexViaLookupAndShift : 86,926.87 평균 틱 (1000 회 이상), 10.2X

ByteArrayToHexStringViaBitConverter : 139,353.73 평균 틱 (1000 회 이상), 6.3X

ByteArrayToHexViaSoapHexBinary : 314,598.77 평균 틱 (1000 회 이상), 2.8X

ByteArrayToHexStringViaStringBuilderForEachByteToString : 344,264.63 평균 틱 (1000 회 이상), 2.6X

ByteArrayToHexStringViaStringBuilderAggregateByteToString : 382,623.44 평균 틱 (1000 회 이상), 2.3X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat : 818,111.95 평균 틱 (1000 회 이상), 1.1X

ByteArrayToHexStringViaStringConcatArrayConvertAll : 839,244.84 평균 틱 (1000 회 이상), 1.1X

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat : 867,303.98 평균 틱 (1000 회 이상), 1.0X

ByteArrayToHexStringViaStringJoinArrayConvertAll : 882,710.28 평균 틱 (1000 회 이상), 1.0X



1

또 다른 빠른 기능 ...

private static readonly byte[] HexNibble = new byte[] {
    0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
    0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};

public static byte[] HexStringToByteArray( string str )
{
    int byteCount = str.Length >> 1;
    byte[] result = new byte[byteCount + (str.Length & 1)];
    for( int i = 0; i < byteCount; i++ )
        result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
    if( (str.Length & 1) != 0 )
        result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
    return result;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.