etc./StackOverFlow

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

청렴결백한 만능 재주꾼 2022. 2. 27. 12:39
반응형

질문자 :Community Wiki


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



어느 하나:

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

또는:

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

예를 들어 여기 와 같이 더 많은 변형이 있습니다.

역변환은 다음과 같이 진행됩니다.

 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 와 함께 사용하는 것이 가장 좋습니다. 자세한 내용은 이 답변 을 참조하세요. 더 나은 성능이 필요한 경우 SubString 을 삭제하기 전에 Convert.ToByte 를 피해야 합니다.


Community Wiki

성능 분석

참고: 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 키를 누르고 출력을 기다립니다(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 .Aggregate보다 빠르지만 BitConverter 여전히 이깁니다.

업데이트(2012-04-03)

3위를 차지한 분석에 Mykroft의 SoapHexBinary

업데이트(2013-01-15)

CodesInChaos의 바이트 조작 답변이 추가되어 1위를 차지했습니다(큰 텍스트 블록에서 큰 차이로).

업데이트(2013-05-23)

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

업데이트(2014-07-31)

@CodesInChaos의 새로운 바이트 기반 조회 답변을 추가했습니다. 문장 테스트와 전체 텍스트 테스트 모두에서 선두를 차지한 것으로 보입니다.

업데이트(2015-08-20)

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


patridge

정확히 원하는 것을 수행하는 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(); }

Community Wiki

암호화 코드를 작성할 때 데이터 종속 타이밍이 부채널 공격으로 이어질 수 있기 때문에 런타임이 데이터에 종속되지 않도록 하기 위해 데이터 종속 분기 및 테이블 조회를 피하는 것이 일반적입니다.

그것도 꽤 빠릅니다.

 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
    b < 10 값에 대해 < 0 이며, 이는 십진수가 됩니다.
    b > 10 >= 0 A 에서 F 까지의 문자가 됩니다.
  3. i >> 31 을 사용하면 부호 확장 덕분에 부호를 추출합니다. 그것은 것이다 -1 위해 i < 00 에 대해 i >= 0 .
  4. 2)와 3)을 결합하면 (b-10)>>31 이 문자의 0 이고 숫자의 경우 -1
  5. 문자의 경우를 살펴보면 마지막 summand는 0 이 되고 b 는 10에서 15 사이의 범위에 있습니다. A (65)에서 F 'A'-10 )를 더하는 것을 의미합니다. .
  6. b 를 0에서 9까지의 범위에서 0 (48)에서 9 (57)까지의 범위로 매핑하도록 마지막 합계를 적용하려고 합니다. '0' - 55 )이 되어야 함을 의미합니다.
    이제 우리는 7을 곱할 수 있습니다. 그러나 -1은 모든 비트가 1인 것으로 표시 (0 & -7) == 0(-1 & -7) == -7 이후로 & -7 대신 사용할 수 있습니다.

몇 가지 추가 고려 사항:

  • i 에서 계산하는 것이 더 저렴하다는 것을 보여주기 때문에 두 번째 루프 변수를 사용하여 c
  • 루프의 상한선으로 정확히 i < bytes.Length bytes[i] 에 대한 경계 검사를 제거할 수 있으므로 해당 변형을 선택했습니다.
  • b 를 int로 만들면 바이트에서 바이트로 불필요한 변환이 가능합니다.

Community Wiki

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

(원본 게시물의 댓글에서 후자입니다.)


Community Wiki

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

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

Community Wiki

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

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

산출:

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

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


Community Wiki

오늘 방금 동일한 문제가 발생했고 다음 코드를 발견했습니다.

 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[] Array to Hex String (PZahra의 게시물 참조). 0x 접두사를 제거하기 위해 코드를 약간 수정했습니다.

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


Community Wiki

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

이 편집이 잘못된 경우를 만들고 되돌릴 수 있는 이유를 설명하겠습니다. 그 과정에서 일부 내부에 대해 한두 가지를 배우고 조기 최적화가 실제로 무엇이며 어떻게 문제를 일으킬 수 있는지에 대한 또 다른 예를 볼 수 있습니다.

tl;dr: 급한 경우에는 Convert.ToByteString.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 에 전달할 문자열을 할당해야 합니다. 더욱이, 개정판에서 제공되는 솔루션은 모든 반복에서 또 다른 객체를 할당합니다(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 는 두 자리(기호)를 사용하여 단일 옥텟을 나타냅니다.

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

남은 것은 "값"만 추가된 문자열 판독기입니다. 병렬 인덱스( j ), 중복 길이 변수(internal _length ) 및 중복 참조를 선언할 수 _pos 입력 문자열(내부 _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; }

i 평행하게 2단계씩 증가 j 인덱스가 정말로 필요합니까? 물론 아닙니다. i 에 2를 곱하면 됩니다(컴파일러가 덧셈에 대해 최적화할 수 있어야 함).

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

예!

Props to Partridge for the bench framework, 그것은 해킹하기 쉽습니다. 사용된 입력은 100,000바이트 길이의 문자열을 만들기 위해 5000번 반복되는 다음 SHA-1 해시입니다.

 209113288F93A9AB8E474EA78D899AFDBB874355

즐거운 시간 보내세요! (그러나 적당히 최적화하십시오.)


Community Wiki

.NET 5 RC2부터 다음을 사용할 수 있습니다.

스팬 매개변수를 사용하는 과부하를 사용할 수 있습니다.


Community Wiki

@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'의 경우 큰 숫자를 사용해야 하므로 & 0x0f 를 사용하여 0 으로 만들어 기본 버전에서 32를 빼야 합니다.

'A' 대한 코드입니다.

'0' 대한 코드입니다.

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


Community Wiki

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

  • 인코더 테이블 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). 스트림 또는 배열을 디코딩/인코딩하는 방법을 구현해야 하며 이는 개념 증명일 뿐입니다.


Community Wiki

왜 복잡하게 만드나요? Visual Studio 2008에서는 다음과 같이 간단합니다.

씨#:

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

VB:

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

Community Wiki

이것은 훌륭한 게시물입니다. 나는 Waleed의 솔루션을 좋아합니다. patridge의 테스트를 통해 실행하지 않았지만 꽤 빠른 것 같습니다. 또한 16진수 문자열을 바이트 배열로 변환하는 역 과정이 필요했기 때문에 Waleed의 솔루션을 역순으로 작성했습니다. Tomalak의 원래 솔루션보다 빠른지 확실하지 않습니다. 다시 말하지만 패트리지의 테스트를 통해서도 역과정을 실행하지 않았습니다.

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

Community Wiki

닷넷 5 업데이트

byte[] (바이트 배열)에서 16진수 string 로 변환하려면 다음을 사용하십시오.

System.Convert.ToHexString

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

string 에서 byte[] 로 변환하려면 다음을 사용하십시오.

System.Convert.FromHexString

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

Community Wiki

여기에 많은 답변을 쌓지 않고 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은 잘 다루어지는 것 같습니다. 따라서이 답변입니다.)


Community Wiki

안전한 버전:

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

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


Community Wiki

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

Community Wiki

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


Community Wiki

확장 방법 (면책 조항: 완전히 테스트되지 않은 코드, 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(); } }

etc.. Tomalak의 세 가지 솔루션 중 하나를 사용하십시오(마지막 솔루션은 문자열에 대한 확장 방법임).


Community Wiki

올드 스쿨 사람들을 위한 가장 빠른 방법... 그리워요 포인터

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

Community Wiki

.NET 5에는 Convert.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


Community Wiki

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

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

Community Wiki

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

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

Community Wiki

나는 당신이 작동하도록 제안한 코드를 얻지 못했습니다, Olipro. hex[i] + hex[i+1] int 반환했습니다.

그러나 나는 Waledes 코드에서 몇 가지 힌트를 얻고 이것을 함께 망치질하여 약간의 성공을 거두었습니다. 지옥처럼 추악하지만 내 테스트(패트리지 테스트 메커니즘 사용)에 따라 다른 것들에 비해 1/3의 시간으로 작동하고 수행하는 것 같습니다. 입력 크기에 따라 다릅니다. 문자보다 숫자가 더 많기 때문에 ?: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; }

Community Wiki

이 버전의 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); }

Community Wiki

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

자바 코드에서 변환.


Community Wiki

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

분명히 두 가지 메서드 호출은 비용이 많이 듭니다. 입력 또는 출력 데이터(CRC, 체크섬 또는 무엇이든 될 수 있음)에 대해 일종의 검사가 수행되는 if (b == 255)... 건너뛸 수 있으므로 메서드가 모두 호출됩니다.

offsetoffset + 1 대신 offset++offset 사용하면 이론적 이점이 있을 수 있지만 컴파일러가 이 문제를 나보다 더 잘 처리하는 것 같습니다.

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

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


Community Wiki

다양성을 위한 또 다른 변형:

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

Community Wiki

속도에 최적화되어 있지는 않지만 대부분의 답변(.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

Community Wiki

출처 : http:www.stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa

반응형