Como você pode converter uma matriz de bytes em uma seqüência hexadecimal e vice-versa?
Como você pode converter uma matriz de bytes em uma seqüência hexadecimal e vice-versa?
Respostas:
Ou:
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();
}
ou:
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-","");
}
Existem ainda mais variantes, por exemplo aqui .
A conversão reversa seria assim:
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;
}
Usar Substring
é a melhor opção em combinação com Convert.ToByte
. Veja esta resposta para mais informações. Se você precisar de um melhor desempenho, evite Convert.ToByte
antes que possa cair SubString
.
Nota: novo líder a partir de 20/08/2015.
Eu executei cada um dos vários métodos de conversão por meio de alguns Stopwatch
testes de desempenho bruto , uma execução com uma sentença aleatória (n = 61, 1000 iterações) e uma execução com um texto do Project Gutenburg (n = 1.238.957, 150 iterações). Aqui estão os resultados, aproximadamente do mais rápido para o mais lento. Todas as medidas estão em ticks ( 10.000 ticks = 1 ms ) e todas as notas relativas são comparadas com a StringBuilder
implementação [mais lenta] . Para o código usado, veja abaixo ou o repositório da estrutura de teste, onde agora mantenho o código para executar isso.
AVISO: Não confie nessas estatísticas para nada concreto; eles são simplesmente uma amostra de dados de amostra. Se você realmente precisa de desempenho de alto nível, teste esses métodos em um ambiente representativo de suas necessidades de produção com dados representativos do que você usará.
unsafe
(via CodesInChaos) (adicionada ao repositório de teste pelo respirador de ar )
BitConverter
(via Tomalak)
{SoapHexBinary}.ToString
(via Mykroft)
{byte}.ToString("X2")
(using foreach
) (derivado da resposta de Will Dean)
{byte}.ToString("X2")
(usando {IEnumerable}.Aggregate
, requer System.Linq) (via Mark)
Array.ConvertAll
(usando string.Join
) (via Will Dean)
Array.ConvertAll
(usando string.Concat
, requer .NET 4.0) (via Will Dean)
{StringBuilder}.AppendFormat
(usando foreach
) (via Tomalak)
{StringBuilder}.AppendFormat
(usando {IEnumerable}.Aggregate
, requer System.Linq) (derivado da resposta de Tomalak)
As tabelas de pesquisa assumiram a liderança na manipulação de bytes. Basicamente, existe alguma forma de pré-computar o que qualquer mordidela ou byte será em hexadecimal. Então, conforme você percorre os dados, basta procurar a próxima parte para ver qual seria a sequência hexadecimal. Esse valor é então adicionado à saída resultante da string de alguma maneira. Por um longo tempo, a manipulação de bytes, potencialmente mais difícil de ler por alguns desenvolvedores, foi a abordagem de melhor desempenho.
Sua melhor aposta ainda será encontrar alguns dados representativos e testá-los em um ambiente semelhante à produção. Se você tiver restrições de memória diferentes, poderá preferir um método com menos alocações a um método que seria mais rápido, mas consumiria mais memória.
Sinta-se livre para jogar com o código de teste que eu usei. Uma versão está incluída aqui, mas fique à vontade para clonar o repositório e adicionar seus próprios métodos. Envie uma solicitação pull se encontrar algo interessante ou quiser ajudar a melhorar a estrutura de teste usada.
Func<byte[], string>
) a /Tests/ConvertByteArrayToHexString/Test.cs.TestCandidates
valor de retorno na mesma classe.GenerateTestInput
mesma classe.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();
}
Adicionada a resposta de Waleed à análise. Muito rápido.
string.Concat
Array.ConvertAll
Variante adicionada para completude (requer .NET 4.0). A par da string.Join
versão.
O repositório de teste inclui mais variantes, como StringBuilder.Append(b.ToString("X2"))
. Nenhum perturbou os resultados. foreach
é mais rápido do que {IEnumerable}.Aggregate
, por exemplo, mas BitConverter
ainda ganha.
Adicionada a SoapHexBinary
resposta de Mykroft à análise, que assumiu o terceiro lugar.
Adicionada a resposta de manipulação de bytes do CodesInChaos, que assumiu o primeiro lugar (por uma grande margem em grandes blocos de texto).
Adicionada a resposta de pesquisa de Nathan Moinvaziri e a variante do blog de Brian Lambert. Ambos bastante rápidos, mas não assumindo a liderança na máquina de teste que usei (AMD Phenom 9750).
Adicionada a nova resposta de pesquisa baseada em byte do @ CodesInChaos. Parece ter assumido a liderança nos testes de sentenças e nos testes de texto completo.
Adicionadas otimizações e variantes do respirador de ar ao repositóriounsafe
desta resposta . Se você quiser jogar no jogo inseguro, poderá obter enormes ganhos de desempenho em relação a qualquer um dos vencedores anteriores, tanto em textos curtos quanto em textos grandes.
bytes.ToHexStringAtLudicrousSpeed()
).
Há uma classe chamada SoapHexBinary que faz exatamente o que você deseja.
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();
}
Ao escrever código criptográfico, é comum evitar ramificações dependentes de dados e pesquisas de tabela para garantir que o tempo de execução não dependa dos dados, pois o tempo dependente dos dados pode levar a ataques de canal lateral.
Também é bem rápido.
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
Abandonar toda a esperança vós que entrais aqui
Uma explicação do pouco estranho:
bytes[i] >> 4
extrai a mordidela alta de um byte bytes[i] & 0xF
extrai a mordidela baixa de um byteb - 10
< 0
para valores b < 10
, que se tornará um dígito decimal, >= 0
para valores b > 10
, que se tornará uma letra de A
paraF
.i >> 31
de um inteiro assinado de 32 bits extrai o sinal, graças à extensão do sinal. Será -1
por i < 0
e 0
parai >= 0
.(b-10)>>31
será 0
para letras e-1
dígitos.0
e b
está no intervalo de 10 a 15. Queremos mapeá-la para A
(65) a F
(70), o que implica adicionar 55 ('A'-10
).b
do intervalo de 0 a 9 para o intervalo 0
(48) a 9
(57). Isso significa que ele precisa se tornar -7 ( '0' - 55
). & -7
desde (0 & -7) == 0
e (-1 & -7) == -7
.Algumas considerações adicionais:
c
, pois a medição mostra que o cálculo a partir dei
é mais barato.i < bytes.Length
como o limite superior do loop permite que o JITter elimine as verificações de limites bytes[i]
, então eu escolhi essa variante.b
um int permite conversões desnecessárias de e para byte.hex string
para byte[] array
?
87 + b + (((b-10)>>31)&-39)
byte[] array
", que literalmente significa uma matriz de matrizes de bytes, ou byte[][]
. Eu estava apenas brincando.
Se você deseja mais flexibilidade do que BitConverter
, mas não deseja aqueles loops explícitos desajeitados no estilo dos anos 90, você pode:
String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));
Ou, se você estiver usando o .NET 4.0:
String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));
(Este último de um comentário na postagem original.)
Outra abordagem baseada em tabela de pesquisa. Este usa apenas uma tabela de pesquisa para cada byte, em vez de uma tabela de pesquisa por mordidela.
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);
}
Também testei variantes deste usando ushort
, struct{char X1, X2}
, struct{byte X1, X2}
na tabela de pesquisa.
Dependendo do destino da compilação (x86, X64), eles tiveram aproximadamente o mesmo desempenho ou foram um pouco mais lentos que essa variante.
E para um desempenho ainda mais alto, seu unsafe
irmão:
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);
}
Ou se você considera aceitável escrever diretamente na string:
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
pode ser usado agora em vez de unsafe
??
Você pode usar o método BitConverter.ToString:
byte[] bytes = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}
Console.WriteLine( BitConverter.ToString(bytes));
Resultado:
00-01-02-04-08-10-20-40-80-FF
Mais informações: Método BitConverter.ToString (Byte [])
Acabei de encontrar o mesmo problema hoje e me deparei com este código:
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);
}
Fonte: Post do fórum byte [] Array to Hex String (veja o post de PZahra). Modifiquei um pouco o código para remover o prefixo 0x.
Fiz alguns testes de desempenho para o código e foi quase oito vezes mais rápido que o BitConverter.ToString () (o mais rápido de acordo com a publicação de patridge).
Esta é uma resposta à revisão 4 da resposta altamente popular de Tomalak (e edições subsequentes).
Argumentarei que esta edição está incorreta e explico por que ela pode ser revertida. Ao longo do caminho, você pode aprender uma coisa ou duas sobre alguns componentes internos e ver mais um exemplo do que realmente é a otimização prematura e como ela pode te morder.
tl; dr: Basta usar Convert.ToByte
e String.Substring
se você estiver com pressa ("Código original" abaixo), é a melhor combinação se você não deseja reimplementar Convert.ToByte
. Use algo mais avançado (veja outras respostas) que não será usado Convert.ToByte
se você precisar de desempenho. Você não usar qualquer outra coisa que não seja String.Substring
em combinação com Convert.ToByte
, a menos que alguém tem algo interessante a dizer sobre isso nos comentários desta resposta.
aviso: Esta resposta pode se tornar obsoleta se uma Convert.ToByte(char[], Int32)
sobrecarga for implementada na estrutura. É improvável que isso aconteça em breve.
Como regra geral, não gosto muito de dizer "não otimize prematuramente", porque ninguém sabe quando é "prematuro". A única coisa que você deve considerar ao decidir se deve otimizar ou não é: "Eu tenho tempo e recursos para investigar adequadamente as abordagens de otimização?". Caso contrário, é muito cedo, aguarde até que seu projeto esteja mais maduro ou até que você precise do desempenho (se houver uma necessidade real, você poderá dedicar tempo). Enquanto isso, faça a coisa mais simples possível.
Código original:
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;
}
Revisão 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;
}
A revisão evita String.Substring
e usa umStringReader
vez disso. O motivo é:
Editar: você pode melhorar o desempenho de seqüências longas usando um analisador de passagem única, da seguinte maneira:
Bem, olhando o código de referência paraString.Substring
, já é claramente "passagem única"; e por que não deveria ser? Opera em nível de bytes, não em pares substitutos.
No entanto, ele aloca uma nova string, mas é necessário alocar uma para a qual passar Convert.ToByte
. Além disso, a solução fornecida na revisão aloca outro objeto em cada iteração (a matriz de dois caracteres); você pode colocar com segurança essa alocação fora do loop e reutilizar a matriz para evitar isso.
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;
}
Cada hexadecimal numeral
representa um único octeto usando dois dígitos (símbolos).
Mas então, por que ligar StringReader.Read
duas vezes? Apenas chame sua segunda sobrecarga e peça para ler dois caracteres na matriz de dois caracteres ao mesmo tempo; e reduza a quantidade de chamadas em dois.
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;
}
O que resta é um leitor de strings cujo único "valor" adicionado é um índice paralelo (interno _pos
) que você poderia ter se declarado (como j
por exemplo), uma variável de comprimento redundante (interna _length
) e uma referência redundante à entrada string (interna _s
). Em outras palavras, é inútil.
Se você quer saber como Read
"lê", basta olhar para o código , tudo o que faz é chamar String.CopyTo
a string de entrada. O resto é apenas uma sobrecarga de contabilidade para manter valores que não precisamos.
Portanto, remova o leitor de cordas e chame a CopyTo
si mesmo; é mais simples, mais claro e mais eficiente.
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;
}
Você realmente precisa de um j
índice que incrementa em etapas de dois paralelos a i
? Claro que não, basta multiplicar i
por dois (que o compilador deve poder otimizar para uma adição).
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;
}
Como é a solução agora? Exatamente como era no começo, apenas em vez de usar String.Substring
para alocar a sequência e copiar os dados, você está usando uma matriz intermediária na qual copia os números hexadecimais, depois aloca a sequência e copia os dados novamente de a matriz e na sequência (quando você a passa no construtor de sequência). A segunda cópia pode ser otimizada se a sequência já estiver no pool interno, mas String.Substring
também poderá evitá-la nesses casos.
De fato, se você olhar String.Substring
novamente, verá que ele usa algum conhecimento interno de baixo nível de como as strings são construídas para alocar a string mais rapidamente do que você normalmente faria, e alinha o mesmo código usado CopyTo
diretamente por lá para evitar a ligação em cima.
String.Substring
Método manual
Conclusão? Se você deseja usarConvert.ToByte(String, Int32)
(porque você não deseja reimplementar essa funcionalidade), não parece haver uma maneira de superarString.Substring
; tudo o que você faz é correr em círculos, reinventando a roda (apenas com materiais abaixo do ideal).
Observe que usar Convert.ToByte
e String.Substring
é uma opção perfeitamente válida se você não precisar de desempenho extremo. Lembre-se: só opte por uma alternativa se você tiver tempo e recursos para investigar como ela funciona corretamente.
Se houvesse um Convert.ToByte(char[], Int32)
, as coisas seriam diferentes, é claro (seria possível fazer o que descrevi acima e evitar completamente String
).
Suspeito que as pessoas que relatam melhor desempenho "evitando String.Substring
" também evitem Convert.ToByte(String, Int32)
, o que você realmente deveria estar fazendo se, de qualquer maneira, precisar do desempenho. Veja as inúmeras outras respostas para descobrir todas as diferentes abordagens para fazer isso.
Isenção de responsabilidade: não descompilei a versão mais recente da estrutura para verificar se a fonte de referência está atualizada, presumo que esteja.
Agora, tudo parece bom e lógico, espero até óbvio se você conseguiu chegar até agora. Mas é verdade?
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
Sim!
Adereços para Partridge para a estrutura do banco, é fácil de hackear. A entrada usada é o seguinte hash SHA-1 repetido 5000 vezes para criar uma sequência de 100.000 bytes de comprimento.
209113288F93A9AB8E474EA78D899AFDBB874355
Diverta-se! (Mas otimize com moderação.)
Complemento para responder por @CodesInChaos (método reverso)
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;
}
Explicação:
& 0x0f
é apoiar também letras minúsculas
hi = hi + 10 + ((hi >> 31) & 7);
é o mesmo que:
hi = ch-65 + 10 + (((ch-65) >> 31) & 7);
Para '0' .. '9', é o mesmo hi = ch - 65 + 10 + 7;
que é hi = ch - 48
(isso é devido a 0xffffffff & 7
).
Para 'A' .. 'F' é hi = ch - 65 + 10;
(isso é por causa de 0x00000000 & 7
).
Para 'a' .. 'f', temos que grandes números, portanto devemos subtrair 32 da versão padrão, criando alguns bits 0
usando & 0x0f
.
65 é código para 'A'
48 é código para '0'
7 é o número de letras entre '9'
e 'A'
na tabela ASCII ( ...456789:;<=>?@ABCD...
).
Esse problema também pode ser resolvido usando uma tabela de consulta. Isso exigiria uma pequena quantidade de memória estática para o codificador e o decodificador. Este método, no entanto, será rápido:
Minha solução usa 1024 bytes para a tabela de codificação e 256 bytes para decodificação.
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 *
* esta solução
Durante a decodificação, IOException e IndexOutOfRangeException podem ocorrer (se um caractere tiver um valor muito alto> 256). Métodos para / codificar fluxos ou matrizes devem ser implementados, isto é apenas uma prova de conceito.
Este é um ótimo post. Eu gosto da solução de Waleed. Eu não fiz o teste de patridge, mas parece ser bastante rápido. Eu também precisava do processo inverso, convertendo uma sequência hexadecimal em uma matriz de bytes, então escrevi como uma reversão da solução de Waleed. Não tenho certeza se é mais rápido que a solução original da Tomalak. Mais uma vez, também não executei o processo inverso pelo teste de 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;
Por que torná-lo complexo? Isso é simples no Visual Studio 2008:
C #:
string hex = BitConverter.ToString(YourByteArray).Replace("-", "");
VB:
Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Para não acumular as muitas respostas aqui, mas achei uma implementação direta bastante ótima (~ 4,5x melhor do que o aceito) e direta do analisador de cadeia hexadecimal. Primeiro, saída dos meus testes (o primeiro lote é minha implementação):
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
As linhas base64 e 'BitConverter'd' estão lá para testar a correção. Observe que eles são iguais.
A implementação:
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;
}
Tentei algumas coisas unsafe
e movi a if
sequência de caracteres para mordiscar (claramente redundante) para outro método, mas esse foi o mais rápido possível.
(Admito que isso responda metade da pergunta. Eu senti que a conversão string-> byte [] estava sub-representada, enquanto o ângulo byte [] -> string parece estar bem coberto. Assim, essa resposta.)
Versões seguras:
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;
}
}
}
Versões inseguras Para quem prefere desempenho e não tem medo da insegurança. ToHex 35% mais rápido e FromHex 10% mais rápido.
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 Para teste de benchmark, inicializando o alfabeto toda vez que a função de conversão chamada estiver incorreta, o alfabeto deve ser const (para string) ou estático somente leitura (para char []). Em seguida, a conversão baseada em alfabeto de byte [] em string se torna tão rápida quanto as versões de manipulação de bytes.
E, é claro, o teste deve ser compilado no Release (com otimização) e com a opção de depuração "Suprimir otimização JIT" desativada (o mesmo para "Ativar apenas meu código" se o código precisar ser depurável).
Função inversa para o código Waleed Eissa (Hex String para Byte Array):
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;
}
Função Waleed Eissa com suporte a letras minúsculas:
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);
}
Métodos de extensão (exoneração de responsabilidade: código completamente não testado, 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 .. Use uma das três soluções do Tomalak (com a última sendo um método de extensão em uma string).
Dos desenvolvedores da Microsoft, uma conversão simples e agradável:
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();
}
Enquanto o acima é limpo e compacto, os viciados em desempenho gritarão sobre isso usando enumeradores. Você pode obter desempenho máximo com uma versão aprimorada da resposta original do 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();
}
Esta é a mais rápida de todas as rotinas que eu vi postadas aqui até agora. Não basta acreditar na minha palavra: teste de desempenho em cada rotina e inspecione seu código CIL.
b.ToSting("X2")
.
E para inserir em uma string SQL (se você não estiver usando parâmetros de comando):
public static String ByteArrayToSQLHexString(byte[] Source)
{
return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}
Source == null
ou Source.Length == 0
temos um problema, senhor!
Em termos de velocidade, isso parece ser melhor do que qualquer coisa aqui:
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);
}
Não recebi o código que você sugeriu para trabalhar, Olipro. hex[i] + hex[i+1]
aparentemente retornou um int
.
No entanto, obtive algum sucesso, pegando algumas dicas do código Waleeds e martelando isso juntas. É feio como o inferno, mas parece funcionar e funciona em 1/3 do tempo em comparação com os outros de acordo com meus testes (usando o mecanismo de teste de patridges). Dependendo do tamanho da entrada. Alternar entre?: S para separar 0-9 primeiro provavelmente produziria um resultado um pouco mais rápido, pois há mais números do que letras.
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;
}
Esta versão do ByteArrayToHexViaByteManipulation pode ser mais rápida.
Dos meus relatórios:
...
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);
}
E acho que essa é uma otimização:
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);
}
Entrarei nesta competição de manipulação de bits, pois tenho uma resposta que também usa a manipulação de bits para decodificar hexadecimais. Observe que o uso de matrizes de caracteres pode ser ainda mais rápido, pois os StringBuilder
métodos de chamada também levarão tempo.
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;
}
Convertido do código Java.
Char[]
e usar Char
internamente em vez de ints ...
Para desempenho, eu usaria a solução drphrozens. Uma pequena otimização para o decodificador pode ser usar uma tabela para qualquer um dos caracteres para se livrar do "<< 4".
Claramente, as duas chamadas de método são caras. Se algum tipo de verificação for feita nos dados de entrada ou saída (pode ser CRC, soma de verificação ou o que for), oif (b == 255)...
pode ser ignorado e, assim, também o método chama completamente.
Usar offset++
e em offset
vez de offset
e offset + 1
pode dar algum benefício teórico, mas suspeito que o compilador lida com isso melhor do que eu.
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]));
}
Isso está no topo da minha cabeça e não foi testado ou comparado.
Outra variação para a diversidade:
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;
}
Não otimizado para velocidade, mas mais LINQy que a maioria das respostas (.NET 4.0):
<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
Dois mashups que dobram as duas operações de mordidelas em uma.
Versão provavelmente bastante eficiente:
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 );
}
Versão linq com hackers decadente:
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))) );
}
E inverter:
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;
}
Outra maneira é usar stackalloc
para reduzir a pressão de memória do 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);
}
Aqui está minha chance. Eu criei um par de classes de extensão para estender string e byte. No teste de arquivos grandes, o desempenho é comparável ao Byte Manipulation 2.
O código abaixo para ToHexString é uma implementação otimizada do algoritmo de pesquisa e deslocamento. É quase idêntico ao de Behrooz, mas acaba usando a foreach
para iterar e um contador é mais rápido que uma indexação explícitafor
.
Ele vem em 2º lugar atrás do Byte Manipulation 2 na minha máquina e é um código muito legível. Os seguintes resultados do teste também são interessantes:
ToHexStringCharArrayWithCharArrayLookup: 41.589,69 ticks médios (mais de 1000 execuções), 1,5X ToHexStringCharArrayWithStringLookup: 50,764.06 ticks médios (mais de 1000 execuções), 1,2X ToHexStringStringBuilderWithCharArrayLookup: 62,812,87 ticks médios (mais de 1000 execuções)
Com base nos resultados acima, parece seguro concluir que:
Aqui está o código:
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;
}
}
}
Abaixo estão os resultados dos testes que obtive quando coloquei meu código no projeto de teste do @ patridge na minha máquina. Também adicionei um teste para converter em uma matriz de bytes de hexadecimal. As execuções de teste que exercitaram meu código são ByteArrayToHexViaOptimizedLookupAndShift e HexToByteArrayViaByteManipulation. O HexToByteArrayViaConvertToByte foi retirado de XXXX. O HexToByteArrayViaSoapHexBinary é o da resposta da @ Mykroft.
Processador Intel Pentium III Xeon
Cores: 4 <br/> Current Clock Speed: 1576 <br/> Max Clock Speed: 3092 <br/>
Convertendo matriz de bytes em representação de cadeia hexadecimal
ByteArrayToHexViaByteManipulation2: 39.366,64 ticks médios (mais de 1000 execuções), 22,4X
ByteArrayToHexViaOptimizedLookupAndShift: 41.588,64 ticks médios (mais de 1000 execuções), 21,2X
ByteArrayToHexViaLookup: 55.509,56 ticks médios (mais de 1000 execuções), 15,9X
ByteArrayToHexViaByteManipulation: 65.349,12 ticks médios (mais de 1000 execuções), 13,5X
ByteArrayToHexViaLookupAndShift: 86.926,87 ticks médios (mais de 1000 execuções), 10,2X
ByteArrayToHexStringViaBitConverter: 139.353,73 ticks médios (mais de 1000 execuções), 6,3X
ByteArrayToHexViaSoapHexBinary: 314.598,77 ticks médios (mais de 1000 execuções), 2,8X
ByteArrayToHexStringViaStringBuilderForEachByteToString: 344.264,63 ticks médios (mais de 1000 execuções), 2,6X
ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382.623,44 ticks médios (mais de 1000 execuções), 2,3X
ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818.111,95 ticks médios (mais de 1000 execuções), 1,1X
ByteArrayToHexStringViaStringConcatArrayConvertAll: 839.244,84 ticks médios (mais de 1000 execuções), 1,1X
ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867.303,98 ticks médios (mais de 1000 execuções), 1,0X
ByteArrayToHexStringViaStringJoinArrayConvertAll: 882.710,28 ticks médios (mais de 1000 execuções), 1,0X
Outra função rápida ...
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;
}