답변:
어느 한 쪽:
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
또는:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
여기에 더 많은 변형이 있습니다 (예 : here) .
역변환은 다음과 같습니다.
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
Substring
와 함께 사용하는 것이 가장 좋습니다 Convert.ToByte
. 자세한 내용은 이 답변 을 참조하십시오. 더 나은 성능이 필요한 경우, Convert.ToByte
삭제하기 전에 피해야합니다 SubString
.
참고 : 2015-08-20 기준 새로운 지도자.
나는 몇 가지 조잡한 Stopwatch
성능 테스트, 임의 문장 실행 (n = 61, 1000 반복) 및 Project Gutenburg 텍스트 실행 (n = 1,238,957, 150 반복)을 통해 다양한 변환 방법을 각각 실행했습니다. 대략 가장 빠른 결과에서 가장 느린 결과까지의 결과는 다음과 같습니다. 모든 측정은 눈금 ( 10,000 틱 = 1ms )으로 이루어지며 모든 상대 음표는 [가장 느린] StringBuilder
구현 과 비교됩니다 . 사용 된 코드는 아래 또는 테스트 프레임 워크 저장소를 참조하십시오 .
경고 : 구체적인 통계를 위해이 통계에 의존하지 마십시오. 그것들은 단순히 샘플 데이터의 샘플 실행입니다. 최고 수준의 성능이 실제로 필요한 경우 프로덕션 요구를 나타내는 환경에서 사용할 데이터를 나타내는 데이터를 사용하여 이러한 방법을 테스트하십시오.
unsafe
(CodeInChaos를 통해) ( 에어 브레 더로 저장소를 테스트하기 위해 추가됨 )
BitConverter
(토 말락 경유)
{SoapHexBinary}.ToString
(Mykroft를 통해)
{byte}.ToString("X2")
(을 사용하여 foreach
) (Will Dean의 답변에서 파생 됨)
{byte}.ToString("X2")
(를 사용하여 {IEnumerable}.Aggregate
System.Linq 필요)
Array.ConvertAll
(사용 string.Join
) (윌 딘 경유)
Array.ConvertAll
(을 사용 string.Concat
하고 .NET 4.0 필요) (Will Dean을 통해)
{StringBuilder}.AppendFormat
(를 사용하여 foreach
) (Tomalak을 통해)
{StringBuilder}.AppendFormat
(을 사용 {IEnumerable}.Aggregate
하면 System.Linq가 필요합니다) (Tomalak의 답변에서 파생 됨)
조회 테이블이 바이트 오버 조작을 주도했습니다. 기본적으로 어떤 주어진 니블이나 바이트가 16 진수인지 미리 계산하는 형태가 있습니다. 그런 다음 데이터를 훑어 보면 다음 부분을 찾아 16 진 문자열이 무엇인지 확인하면됩니다. 그런 다음 해당 값이 어떤 방식으로 결과 문자열 출력에 추가됩니다. 오랜 시간 바이트 조작의 경우 일부 개발자가 읽기 어려울 가능성이 가장 높은 방법이었습니다.
최선의 방법은 여전히 대표적인 데이터를 찾아 프로덕션과 같은 환경에서 시도하는 것입니다. 다른 메모리 제약 조건이있는 경우 더 빠르지 만 더 많은 메모리를 소비하는 방법에 대한 할당량이 적은 방법을 선호 할 수 있습니다.
내가 사용한 테스트 코드로 자유롭게 연주하십시오. 여기에 버전이 포함되어 있지만 리포지토리 를 복제하고 원하는 방식으로 추가하십시오. 흥미있는 것이 있거나 사용하는 테스트 프레임 워크를 향상 시키려면 풀 요청을 제출하십시오.
Func<byte[], string>
)를 /Tests/ConvertByteArrayToHexString/Test.cs에 추가하십시오 .TestCandidates
동일한 클래스 의 리턴 값에 추가하십시오 .GenerateTestInput
동일한 클래스에서 주석을 토글하여 원하는 입력 버전, 문장 또는 텍스트를 실행하고 있는지 확인하십시오 .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();
}
Waleed의 분석에 대한 답변을 추가했습니다. 꽤 빠릅니다.
string.Concat
Array.ConvertAll
완전성을위한 변형이 추가 되었습니다 (.NET 4.0 필요). 파와 string.Join
버전.
테스트 저장소에는와 같은 추가 변종이 포함되어 StringBuilder.Append(b.ToString("X2"))
있습니다. 결과가 전혀 화 나지 않습니다. 예를 들어 foreach
보다 빠르지 {IEnumerable}.Aggregate
만 BitConverter
여전히 승리합니다.
Mykroft의 SoapHexBinary
분석에 대한 답변을 추가 하여 3 위를 차지했습니다.
InChaos의 바이트 조작 응답이 추가되었습니다 (대부분의 텍스트 블록에서 큰 차이로).
Nathan Moinvaziri의 조회 답변과 Brian Lambert의 블로그 변형을 추가했습니다. 둘 다 빠르지 만 내가 사용한 테스트 머신 (AMD Phenom 9750)에서 주도권을 잡지 않았습니다.
@CodesInChaos의 새로운 바이트 기반 조회 응답이 추가되었습니다. 문장 테스트와 전체 텍스트 테스트 모두에서 주도적 인 역할을 한 것으로 보입니다.
이 답변의 repo에 airbreather의 최적화 및 unsafe
변형을 추가 했습니다 . 안전하지 않은 게임에서 플레이하려면 짧은 문자열과 큰 텍스트 모두에서 이전의 최고 우승자에 비해 큰 성능 향상을 얻을 수 있습니다.
bytes.ToHexStringAtLudicrousSpeed()
.
SoapHexBinary 라는 클래스 가 있으며 원하는 것을 정확하게 수행합니다.
using System.Runtime.Remoting.Metadata.W3cXsd2001;
public static byte[] GetStringToBytes(string value)
{
SoapHexBinary shb = SoapHexBinary.Parse(value);
return shb.Value;
}
public static string GetBytesToString(byte[] value)
{
SoapHexBinary shb = new SoapHexBinary(value);
return shb.ToString();
}
암호화 코드를 작성할 때 데이터 종속 분기 및 테이블 조회를 피하여 런타임이 데이터에 의존하지 않도록하는 것이 일반적입니다. 데이터 종속 타이밍은 부 채널 공격을 유발할 수 있기 때문입니다.
꽤 빠릅니다.
static string ByteToHexBitFiddle(byte[] bytes)
{
char[] c = new char[bytes.Length * 2];
int b;
for (int i = 0; i < bytes.Length; i++) {
b = bytes[i] >> 4;
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
b = bytes[i] & 0xF;
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
}
return new string(c);
}
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
모든 희망을 버리고 여기에 들어가는 너희
이상한 비트 피들 링에 대한 설명 :
bytes[i] >> 4
바이트의 높은 니블을 bytes[i] & 0xF
추출 바이트 의 낮은 니블을 추출b - 10
< 0
값을 b < 10
소수점 자리가 될 것 >= 0
값 b > 10
편지가 될 것이다, A
에 F
.i >> 31
부호있는 32 비트 정수를 사용하면 부호 확장 덕분에 부호가 추출됩니다. 그것은 될 것입니다 -1
위해 i < 0
및 0
위해 i >= 0
.(b-10)>>31
것임을 알 수 있습니다 .0
-1
0
, 그리고 b
우리가 그것을 매핑 할 10에서 15 사이에 A
(65)에 F
55를 추가하는 것을 의미하는 (70) ( 'A'-10
).b
0에서 9까지의 범위에서 0
(48)에서 9
(57) 까지의 범위를 매핑 하려고합니다 . 이것은 -7 ( '0' - 55
) 이되어야한다는 것을 의미합니다 . & -7
되기 때문에 대신 (0 & -7) == 0
and 를 사용할 수 있습니다 (-1 & -7) == -7
.몇 가지 추가 고려 사항 :
c
측정 결과 값 i
이 저렴 하다는 것을 보여주기 때문에 두 번째 루프 변수를 사용하여 인덱싱하지 않았습니다 .i < bytes.Length
루프의 상한으로 정확하게 사용하면 JITter가 on에 대한 경계 검사를 제거 할 수 bytes[i]
있으므로 해당 변형을 선택했습니다.b
정수를 만들면 바이트를 불필요하게 변환 할 수 있습니다.hex string
에 byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
"는 문자 그대로 바이트 배열의 배열을 의미합니다 byte[][]
. 또는 . 나는 단지 재미를 파고 있었다.
보다 유연 BitConverter
하지만 1990 년대 스타일의 명시 적 루프를 원하지 않는 경우 다음을 수행 할 수 있습니다.
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
또는 .NET 4.0을 사용하는 경우 :
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
후자는 원래 게시물에 대한 의견에서 나온 것입니다.
다른 조회 테이블 기반 접근 방식. 이것은 니블 당 룩업 테이블 대신 각 바이트마다 하나의 룩업 테이블을 사용합니다.
private static readonly uint[] _lookup32 = CreateLookup32();
private static uint[] CreateLookup32()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
}
return result;
}
private static string ByteArrayToHexViaLookup32(byte[] bytes)
{
var lookup32 = _lookup32;
var result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
var val = lookup32[bytes[i]];
result[2*i] = (char)val;
result[2*i + 1] = (char) (val >> 16);
}
return new string(result);
}
나는 또한 사용하여이의 변형을 테스트 ushort
, struct{char X1, X2}
, struct{byte X1, X2}
룩업 테이블에.
컴파일 대상 (x86, X64)에 따라 성능이 거의 같거나이 변형보다 약간 느립니다.
그리고 더 높은 성능을 위해 unsafe
형제는 다음과 같습니다.
private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();
private static uint[] CreateLookup32Unsafe()
{
var result = new uint[256];
for (int i = 0; i < 256; i++)
{
string s=i.ToString("X2");
if(BitConverter.IsLittleEndian)
result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
else
result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
}
return result;
}
public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new char[bytes.Length * 2];
fixed(byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return new string(result);
}
또는 문자열에 직접 쓰는 것이 허용되는 것으로 간주되는 경우 :
public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
var lookupP = _lookup32UnsafeP;
var result = new string((char)0, bytes.Length * 2);
fixed (byte* bytesP = bytes)
fixed (char* resultP = result)
{
uint* resultP2 = (uint*)resultP;
for (int i = 0; i < bytes.Length; i++)
{
resultP2[i] = lookupP[bytesP[i]];
}
}
return result;
}
Span
대신에 지금 사용할 수 있는지 궁금합니다 unsafe
??
오늘 막 똑같은 문제가 발생 하여이 코드를 발견했습니다.
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 배 빠릅니다.
이것은 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
즐기세요! (그러나 적당량으로 최적화하십시오.)
@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...
).
조회 테이블을 사용하여이 문제를 해결할 수도 있습니다. 이를 위해서는 인코더와 디코더 모두에 소량의 정적 메모리가 필요합니다. 그러나이 방법은 빠릅니다.
내 솔루션은 인코딩 테이블에 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보다 큰 경우). 스트림 또는 배열을 디코딩 / 인코딩하는 방법을 구현해야합니다. 이는 개념에 대한 증거 일뿐입니다.
이것은 좋은 게시물입니다. 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;
}
hexString[i] &= ~0x20;
왜 복잡하게 만드나요? 이것은 Visual Studio 2008에서 간단합니다.
씨#:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB :
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
여기에 많은 답변을 쌓아 두지는 않지만 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이 잘 커버되는 것처럼 느꼈습니다. 따라서이 답변.)
안전한 버전 :
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 최적화"가 해제되어 있어야합니다 (코드를 디버깅 할 수 있어야하는 경우 "내 코드 만 활성화"와 동일).
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);
}
확장 방법 (면책 조항 : 완전히 테스트되지 않은 코드, 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의 세 가지 솔루션 중 하나를 사용하십시오 (마지막에서 확장 방법 인 마지막 솔루션 ).
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 코드를 직접 검사하십시오.
b.ToSting("X2")
.
그리고 SQL 문자열에 삽입하는 경우 (명령 매개 변수를 사용하지 않는 경우) :
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Source == null
또는 Source.Length == 0
우리가 문제가 있다면 !
속도면에서 이것은 여기의 어떤 것보다 낫습니다.
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);
}
귀하가 제안한 코드를받지 못했습니다, 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;
}
이 버전의 ByteArrayToHexViaByteManipulation이 더 빠를 수 있습니다.
내 보고서에서 :
...
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);
}
비트-피들 링 (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 코드에서 변환되었습니다.
Char[]
하고 Char
내부적으로 사용해야한다 .
성능을 위해 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]));
}
이것은 내 머리 꼭대기에 있으며 테스트되거나 벤치마킹되지 않았습니다.
다양성에 대한 또 다른 변형 :
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;
}
속도에 최적화되어 있지 않지만 대부분의 답변 (.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
두 개의 니블 연산을 하나로 접는 두 개의 매시업.
아마도 매우 효율적인 버전입니다.
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;
}
또 다른 방법은 stackalloc
GC 메모리 압력을 낮추기 위해 사용 하는 것입니다 .
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);
}
여기 내 총이 있습니다. 문자열과 바이트를 확장하기 위해 확장 클래스 쌍을 만들었습니다. 큰 파일 테스트에서 성능은 바이트 조작 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
위의 결과를 바탕으로 결론을 내리는 것이 안전 해 보입니다.
코드는 다음과 같습니다.
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
또 다른 빠른 기능 ...
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;
}