A resposta aceita abaixo parece alocar uma quantidade horrível de strings na conversão de string para bytes. Eu estou querendo saber como isso afeta o desempenho
Wim Coenen
9
A classe SoapHexBinary faz exatamente o que você quer, eu acho.
Mykroft 31/03/10
Parece-me que fazer duas perguntas em um post não é muito padrão.
SandRock 30/05/19
Respostas:
1353
Ou:
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(ba.Length*2);foreach(byte b in ba)
hex.AppendFormat("{0:x2}", b);return hex.ToString();}
publicstaticbyte[]StringToByteArray(String hex){intNumberChars= hex.Length;byte[] bytes =newbyte[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.ToByteantes que possa cair SubString.
Você está usando SubString. Esse loop não aloca uma quantidade horrível de objetos de string?
Wim Coenen
30
Honestamente - até que reduza drasticamente o desempenho, eu tenderia a ignorar isso e confiar no Runtime e no GC para cuidar dele.
Tomalak
87
Como um byte é dois nibbles, qualquer sequência hexadecimal que represente validamente uma matriz de bytes deve ter uma contagem de caracteres pares. Um 0 não deve ser adicionado em nenhum lugar - para adicionar um, você pode assumir que dados inválidos são potencialmente perigosos. Se alguma coisa, o método StringToByteArray deve lançar um FormatException se a seqüência hexadecimal contiver um número ímpar de caracteres.
David Boike 9/03/10
7
@ 00jt Você deve assumir que F == 0F. É igual a 0F ou a entrada foi cortada e F é realmente o início de algo que você não recebeu. Cabe ao seu contexto fazer essas suposições, mas acredito que uma função de propósito geral deve rejeitar caracteres ímpares como inválidos, em vez de fazer essa suposição para o código de chamada.
David Boike 28/01
11
@DavidBoike A questão não tinha nada a ver com "como lidar com valores de fluxo possivelmente cortados". Está falando de uma String. String myValue = 10.ToString ("X"); myValue é "A" e não "0A". Agora vá ler a string novamente em bytes, opa, você a quebrou.
precisa saber é
488
Análise de desempenho
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 Stopwatchtestes 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 StringBuilderimplementaçã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 Legal
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á.
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.
Código de teste
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.
Adicione o novo método estático ( Func<byte[], string>) a /Tests/ConvertByteArrayToHexString/Test.cs.
Adicione o nome desse método ao TestCandidatesvalor de retorno na mesma classe.
Verifique se você está executando a versão de entrada desejada, frase ou texto, alternando os comentários na GenerateTestInputmesma classe.
Pressione F5e aguarde a saída (um despejo de HTML também é gerado na pasta / bin).
staticstringByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes){returnstring.Join(string.Empty,Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes){returnstring.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));}staticstringByteArrayToHexStringViaBitConverter(byte[] bytes){string hex =BitConverter.ToString(bytes);return hex.Replace("-","");}staticstringByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.Append(b.ToString("X2"))).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.Append(b.ToString("X2"));return hex.ToString();}staticstringByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes){return bytes.Aggregate(newStringBuilder(bytes.Length*2),(sb, b)=> sb.AppendFormat("{0:X2}", b)).ToString();}staticstringByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes){StringBuilder hex =newStringBuilder(bytes.Length*2);foreach(byte b in bytes)
hex.AppendFormat("{0:X2}", b);return hex.ToString();}staticstringByteArrayToHexViaByteManipulation(byte[] bytes){char[] c =newchar[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);}returnnewstring(c);}staticstringByteArrayToHexViaByteManipulation2(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}staticstringByteArrayToHexViaSoapHexBinary(byte[] bytes){SoapHexBinary soapHexBinary =newSoapHexBinary(bytes);return soapHexBinary.ToString();}staticstringByteArrayToHexViaLookupAndShift(byte[] bytes){StringBuilder result =newStringBuilder(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();}staticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_Lookup32,GCHandleType.Pinned).AddrOfPinnedObject();staticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((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;}staticuint[]_Lookup32=Enumerable.Range(0,255).Select(i =>{string s = i.ToString("X2");return((uint)s[0])+((uint)s[1]<<16);}).ToArray();staticstringByteArrayToHexViaLookupPerByte(byte[] bytes){var result =newchar[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);}returnnewstring(result);}staticstringByteArrayToHexViaLookup(byte[] bytes){string[] hexStringTable =newstring[]{"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 =newStringBuilder(bytes.Length*2);foreach(byte b in bytes){
result.Append(hexStringTable[b]);}return result.ToString();}
Atualização (13-01-2010)
Adicionada a resposta de Waleed à análise. Muito rápido.
Atualização (05/10/2011)
string.ConcatArray.ConvertAllVariante adicionada para completude (requer .NET 4.0). A par da string.Joinversão.
Atualização (05-02-2012)
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 BitConverterainda ganha.
Atualização (03/04/2012)
Adicionada a SoapHexBinaryresposta de Mykroft à análise, que assumiu o terceiro lugar.
Atualização (15/01/2013)
Adicionada a resposta de manipulação de bytes do CodesInChaos, que assumiu o primeiro lugar (por uma grande margem em grandes blocos de texto).
Atualização (23-05-2013)
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).
Atualização (31-07-2014)
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.
Atualização (20/08/2015)
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.
Apesar de disponibilizar o código para você fazer o que solicitou por conta própria, atualizei o código de teste para incluir a resposta Waleed. Apesar de todo mal-humorado, é muito mais rápido.
patridge
2
@CodesInChaos Concluído. E ganhou nos meus testes um pouco também. Ainda não pretendo entender completamente nenhum dos principais métodos, mas eles são facilmente ocultos da interação direta.
patridge
6
Essa resposta não tem a intenção de responder à pergunta do que é "natural" ou comum. O objetivo é fornecer às pessoas algumas referências básicas de desempenho, pois, quando você precisa fazer essas conversões, costuma fazer muito. Se alguém precisar de velocidade bruta, basta executar os benchmarks com alguns dados de teste apropriados no ambiente de computação desejado. Em seguida, guarde esse método em um método de extensão em que você nunca procure sua implementação novamente (por exemplo, bytes.ToHexStringAtLudicrousSpeed()).
patridge
2
Acabou de produzir uma implementação baseada em tabela de pesquisa de alto desempenho. Sua variante segura é cerca de 30% mais rápida que a atual líder da minha CPU. As variantes inseguras são ainda mais rápidas. stackoverflow.com/a/24343727/445517
CodesInChaos
244
Há uma classe chamada SoapHexBinary que faz exatamente o que você deseja.
using System.Runtime.Remoting.Metadata.W3cXsd2001;publicstaticbyte[]GetStringToBytes(stringvalue){SoapHexBinary shb =SoapHexBinary.Parse(value);return shb.Value;}publicstaticstringGetBytesToString(byte[]value){SoapHexBinary shb =newSoapHexBinary(value);return shb.ToString();}
SoapHexBinary não é suportado no .NET Core / .NET Standard ...
juFo 11/03
141
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.
staticstringByteToHexBitFiddle(byte[] bytes){char[] c =newchar[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));}returnnewstring(c);}
bytes[i] >> 4extrai a mordidela alta de um byte bytes[i] & 0xFextrai a mordidela baixa de um byte
b - 10
é < 0para valores b < 10, que se tornará um dígito decimal,
é >= 0para valores b > 10, que se tornará uma letra de AparaF .
O uso i >> 31de um inteiro assinado de 32 bits extrai o sinal, graças à extensão do sinal. Será -1por i < 0e 0parai >= 0 .
Combinando 2) e 3), mostra que (b-10)>>31será 0para letras e-1 dígitos.
Observando o caso das letras, a última soma se torna 0e bestá no intervalo de 10 a 15. Queremos mapeá-la para A(65) a F(70), o que implica adicionar 55 ('A'-10 ).
Olhando para o caso dos dígitos, queremos adaptar o último somatório para que ele mapeie bdo intervalo de 0 a 9 para o intervalo 0(48) a 9(57). Isso significa que ele precisa se tornar -7 ( '0' - 55).
Agora poderíamos multiplicar por 7. Mas como -1 é representado por todos os bits serem 1, podemos usar & -7desde (0 & -7) == 0e (-1 & -7) == -7.
Algumas considerações adicionais:
Não usei uma segunda variável de loop para indexar c, pois a medição mostra que o cálculo a partir dei é mais barato.
Usar exatamente i < bytes.Lengthcomo o limite superior do loop permite que o JITter elimine as verificações de limites bytes[i], então eu escolhi essa variante.
Criar bum int permite conversões desnecessárias de e para byte.
Ainda mais curto: String.Concat (Array.ConvertAll (bytes, x => x.ToString ("X2")))
Nestor
14
Ainda mais curto: String.Concat (bytes.Select (b => b.ToString ("X2"))) [.NET4]
Allon Guralnek
14
Apenas responde metade da pergunta.
Sly Gryphon
1
Por que o segundo precisa do .Net 4? String.Concat está em .Net 2.0.
Polyfun
2
esses loops do "estilo anos 90" são geralmente mais rápidos, mas em uma quantidade insignificante o suficiente para que isso não importe na maioria dos contextos. Ainda vale a pena mencionar embora
Austin_Anderson
69
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.
privatestaticreadonlyuint[] _lookup32 =CreateLookup32();privatestaticuint[]CreateLookup32(){var result =newuint[256];for(int i =0; i <256; i++){string s=i.ToString("X2");
result[i]=((uint)s[0])+((uint)s[1]<<16);}return result;}privatestaticstringByteArrayToHexViaLookup32(byte[] bytes){var lookup32 = _lookup32;var result =newchar[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);}returnnewstring(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 unsafeirmão:
privatestaticreadonlyuint[] _lookup32Unsafe =CreateLookup32Unsafe();privatestaticreadonlyuint* _lookup32UnsafeP =(uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();privatestaticuint[]CreateLookup32Unsafe(){var result =newuint[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;}publicstaticstringByteArrayToHexViaLookup32Unsafe(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newchar[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]];}}returnnewstring(result);}
Ou se você considera aceitável escrever diretamente na string:
publicstaticstringByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes){var lookupP = _lookup32UnsafeP;var result =newstring((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;}
Por que a criação da tabela de pesquisa na versão não segura troca os nibbles do byte pré-computado? Eu pensei que endianismo mudou apenas a ordem das entidades formadas por vários bytes.
Raif Atef
@RaifAtef O que importa aqui não é a ordem dos petiscos. Mas a ordem das palavras de 16 bits em um número inteiro de 32 bits. Mas estou pensando em reescrevê-lo para que o mesmo código possa ser executado independentemente da continuidade.
CodesInChaos
Relendo o código, acho que você fez isso porque, quando lança o char * posteriormente para um uint * e o atribui (ao gerar o hex char), o tempo de execução / CPU inverte os bytes (já que o uint não é tratado como o igual a 2 caracteres separados de 16 bits), para pré-lançá-los para compensar. Estou certo ? Endianness é confuso :-).
Raif Atef
4
Isso apenas responde metade da pergunta ... Que tal de hexadecimal para bytes?
Narvalex
3
@CodesInChaos Gostaria de saber se Spanpode ser usado agora em vez de unsafe??
Acabei de encontrar o mesmo problema hoje e me deparei com este código:
privatestaticstringByteArrayToHex(byte[] barray){char[] c =newchar[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);}returnnewstring(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).
para não mencionar que isso usa menos memória. Nenhuma sequência intermediária criada de maneira alguma.
Chochos 16/10/09
8
Apenas responde metade da pergunta.
Sly Gryphon
Isso é ótimo porque funciona basicamente em qualquer versão do NET, incluindo o NETMF. Um vencedor!
Jonesome Reinstate Monica
1
A resposta aceita fornece 2 excelentes métodos HexToByteArray, que representam a outra metade da pergunta. A solução da Waleed responde à questão atual de como fazer isso sem criar um grande número de strings no processo.
Brendten Eickstaedt
A nova string (c) copia e realoca ou é inteligente o suficiente para saber quando pode simplesmente agrupar o char []?
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.ToBytee String.Substringse 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.ToBytese você precisar de desempenho. Você não usar qualquer outra coisa que não seja String.Substringem 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:
publicstaticbyte[]HexadecimalStringToByteArray_Original(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(input.Substring(i *2,2),16);return output;}
Revisão 4:
publicstaticbyte[]HexadecimalStringToByteArray_Rev4(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++)
output[i]=Convert.ToByte(newstring(newchar[2]{(char)sr.Read(),(char)sr.Read()}),16);}return output;}
A revisão evita String.Substringe 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.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){
numeral[0]=(char)sr.Read();
numeral[1]=(char)sr.Read();
output[i]=Convert.ToByte(newstring(numeral),16);}}return output;}
Cada hexadecimal numeralrepresenta um único octeto usando dois dígitos (símbolos).
Mas então, por que ligar StringReader.Readduas 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.
publicstaticbyte[]HexadecimalStringToByteArray(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];
using (var sr =newStringReader(input)){for(var i =0; i < outputLength; i++){var read = sr.Read(numeral,0,2);Debug.Assert(read ==2);
output[i]=Convert.ToByte(newstring(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 jpor 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.CopyToa 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 CopyTosi mesmo; é mais simples, mais claro e mais eficiente.
Você realmente precisa de um jíndice que incrementa em etapas de dois paralelos a i? Claro que não, basta multiplicar ipor dois (que o compilador deve poder otimizar para uma adição).
publicstaticbyte[]HexadecimalStringToByteArray_BestEffort(string input){var outputLength = input.Length/2;var output =newbyte[outputLength];var numeral =newchar[2];for(int i =0; i < outputLength; i++){
input.CopyTo(i *2, numeral,0,2);
output[i]=Convert.ToByte(newstring(numeral),16);}return output;}
Como é a solução agora? Exatamente como era no começo, apenas em vez de usar String.Substringpara 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.Substringtambém poderá evitá-la nesses casos.
De fato, se você olhar String.Substringnovamente, 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 CopyTodiretamente por lá para evitar a ligação em cima.
String.Substring
Na pior das hipóteses: uma alocação rápida, uma cópia rápida.
Melhor caso: sem alocação, sem cópia.
Método manual
Na pior das hipóteses: duas alocações normais, uma cópia normal e uma cópia rápida.
Melhor caso: uma alocação normal, uma cópia normal.
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.ToBytee 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.60GHzCores:8CurrentClockSpeed:2600MaxClockSpeed:2600--------------------Parsing hexadecimal stringinto an array of bytes
--------------------HexadecimalStringToByteArray_Original:7,777.09 average ticks (over 10000 runs),1.2XHexadecimalStringToByteArray_BestEffort:8,550.82 average ticks (over 10000 runs),1.1XHexadecimalStringToByteArray_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.
error: {"Não foi possível encontrar nenhum dígito reconhecível."}
Priya Jagtap
17
Complemento para responder por @CodesInChaos (método reverso)
publicstaticbyte[]HexToByteUsingByteManipulation(string s){byte[] bytes =newbyte[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 0usando & 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:
Tabela de codificadores 512 bytes ou 1024 bytes (duas vezes o tamanho, se forem necessárias maiúsculas e minúsculas)
Tabela de decodificadores 256 bytes ou 64 KiB (uma pesquisa de caractere único ou pesquisa de caractere duplo)
Minha solução usa 1024 bytes para a tabela de codificação e 256 bytes para decodificação.
Decodificação
privatestaticreadonlybyte[]LookupTable=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookup(char c){var b =LookupTable[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(char[] chars,int offset){return(byte)(Lookup(chars[offset])<<4|Lookup(chars[offset +1]));}
Codificação
privatestaticreadonlychar[][]LookupTableUpper;privatestaticreadonlychar[][]LookupTableLower;staticHex(){LookupTableLower=newchar[256][];LookupTableUpper=newchar[256][];for(var i =0; i <256; i++){LookupTableLower[i]= i.ToString("x2").ToCharArray();LookupTableUpper[i]= i.ToString("X2").ToCharArray();}}publicstaticchar[]ToCharLower(byte[] b,int bOffset){returnLookupTableLower[b[bOffset]];}publicstaticchar[]ToCharUpper(byte[] b,int bOffset){returnLookupTableUpper[b[bOffset]];}
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.
O uso de memória de 256 bytes é insignificante quando você executa o código no CLR.
dólmen
9
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.
privatebyte[]HexStringToByteArray(string hexString){int hexStringLength = hexString.Length;byte[] b =newbyte[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;}
Esse código pressupõe que a cadeia hexadecimal usa caracteres alfa em maiúsculas e é acionada se a cadeia hexadecimal usa alfa em minúsculas. Pode querer fazer uma conversão "maiúscula" na string de entrada para ser seguro.
Marc Novakowski
Essa é uma observação astuta, Marc. O código foi escrito para reverter a solução de Waleed. A chamada ToUpper atrasaria um pouco o algoritmo, mas permitiria lidar com caracteres alfabéticos minúsculos.
Chris F
3
Convert.ToByte (topChar + bottomChar) pode ser escrita como (byte) (topChar + bottomChar)
Amir Rezaei
Para lidar com ambos os casos, sem uma grande penalidade de desempenho,hexString[i] &= ~0x20;
Ben Voigt
9
Por que torná-lo complexo? Isso é simples no Visual Studio 2008:
o motivo é o desempenho, quando você precisa de uma solução de alto desempenho. :)
Ricky
7
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:04c63f7842740c77e545bb0b2ade90b384f119f6ab57b680b7aa575a2f40939fTime to parse 100,000 times:50.4192 ms
Resultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FAccepted answer:(StringToByteArray)Time to parse 100000 times:233.1264msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithMono's implementation:Time to parse 100000 times:777.2544msResultas 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-57-B6-80-B7-AA-57-5A-2F-40-93-9FWithSoapHexBinary:Time to parse 100000 times:845.1456msResultas 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-57-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:
publicstaticbyte[]ToByteArrayFromHex(string hexString){if(hexString.Length%2!=0)thrownewArgumentException("String must have an even length");vararray=newbyte[hexString.Length/2];for(int i =0; i < hexString.Length; i +=2){array[i/2]=ByteFromTwoChars(hexString[i], hexString[i +1]);}returnarray;}privatestaticbyteByteFromTwoChars(char p,char p_2){byte ret;if(p <='9'&& p >='0'){
ret =(byte)((p -'0')<<4);}elseif(p <='f'&& p >='a'){
ret =(byte)((p -'a'+10)<<4);}elseif(p <='F'&& p >='A'){
ret =(byte)((p -'A'+10)<<4);}elsethrownewArgumentException("Char is not a hex digit: "+ p,"p");if(p_2 <='9'&& p_2 >='0'){
ret |=(byte)((p_2 -'0'));}elseif(p_2 <='f'&& p_2 >='a'){
ret |=(byte)((p_2 -'a'+10));}elseif(p_2 <='F'&& p_2 >='A'){
ret |=(byte)((p_2 -'A'+10));}elsethrownewArgumentException("Char is not a hex digit: "+ p_2,"p_2");return ret;}
Tentei algumas coisas unsafee movi a ifsequê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.)
Para os seguidores de Knuth: fiz isso porque preciso analisar alguns milhares de cadeias hexadecimais a cada poucos minutos, por isso é importante que seja o mais rápido possível (no loop interno, por assim dizer). A solução da Tomalak não é notavelmente mais lenta se muitas dessas análises não estão ocorrendo.
Ben Mosher
5
Versões seguras:
publicstaticclassHexHelper{[System.Diagnostics.Contracts.Pure]publicstaticstringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring hexAlphabet =@"0123456789ABCDEF";var chars =newchar[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];}}returnnewstring(chars);}[System.Diagnostics.Contracts.Pure]publicstaticbyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[value.Length/2];for(int i =0; i < result.Length; i++){// 0(48) - 9(57) -> 0 - 9// A(65) - F(70) -> 10 - 15int 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.
publicstaticclassHexUnsafeHelper{[System.Diagnostics.Contracts.Pure]publicstaticunsafestringToHex(thisbyte[]value){if(value==null)thrownewArgumentNullException("value");conststring alphabet =@"0123456789ABCDEF";string result =newstring(' ',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]publicstaticunsafebyte[]FromHex(thisstringvalue){if(value==null)thrownewArgumentNullException("value");if(value.Length%2!=0)thrownewArgumentException("Hexadecimal value length must be even.","value");unchecked{byte[] result =newbyte[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 - 15int 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):
publicstaticbyte[]HexToBytes(thisstring hexString){byte[] b =newbyte[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:
publicstaticstringBytesToHex(thisbyte[] barray,bool toLowerCase =true){byte addByte =0x37;if(toLowerCase) addByte =0x57;char[] c =newchar[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);}returnnewstring(c);}
Métodos de extensão (exoneração de responsabilidade: código completamente não testado, BTW ...):
publicstaticclassByteExtensions{publicstaticstringToHexString(thisbyte[] ba){StringBuilder hex =newStringBuilder(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).
Você provavelmente deve testar o código antes de oferecê-lo para uma pergunta como esta.
JWW
3
Dos desenvolvedores da Microsoft, uma conversão simples e agradável:
publicstaticstringByteArrayToString(byte[] ba){// Concatenate the bytes into one long stringreturn ba.Aggregate(newStringBuilder(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 :
publicstaticstringByteArrayToString(byte[] ba){StringBuilder hex =newStringBuilder(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.
se Source == nullou Source.Length == 0temos um problema, senhor!
Andrei Krasutski
2
Em termos de velocidade, isso parece ser melhor do que qualquer coisa aqui:
publicstaticstringToHexString(byte[] data){byte b;int i, j, k;int l = data.Length;char[] r =newchar[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);}returnnewstring(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.
publicstaticbyte[]StringToByteArray2(string hex){byte[] bytes =newbyte[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:
ByteArrayToHexViaByteManipulation3: 1,68 ticks médios (mais de 1000 execuções), 17,5X
ByteArrayToHexViaByteManipulation2: 1,73 ticks médios (mais de 1000 execuções), 16,9X
ByteArrayToHexViaByteManipulation: 2,90 ticks médios (mais de 1000 execuções), 10,1X
ByteArrayToHexViaLookupAndShift: 3,22 ticks médios (mais de 1000 execuções), 9,1X
...
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation3(byte[] bytes){char[] c =newchar[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];}returnnewstring(c);}
E acho que essa é uma otimização:
staticprivatereadonlychar[] hexAlphabet =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};staticstringByteArrayToHexViaByteManipulation4(byte[] bytes){char[] c =newchar[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];}returnnewstring(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 StringBuildermétodos de chamada também levarão tempo.
publicstaticStringToHex(byte[] data){int dataLength = data.Length;// pre-create the stringbuilder using the length of the data * 2, precisely enoughStringBuilder sb =newStringBuilder(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 letterint 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 characterint 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();}publicstaticbyte[]FromHex(String hex){// pre-create the arrayint resultLength = hex.Length/2;byte[] result =newbyte[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 basedvalue=((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){thrownewArgumentException("Hexadecimal encoding incorrect for input "+ hex);}return result;}
Hmm, eu realmente deve otimizar isso para Char[]e usar Charinternamente em vez de ints ...
Maarten Bodewes
Para C #, provavelmente é preferível inicializar as variáveis em que são usadas, em vez de fora do loop, para otimizar o compilador. Eu obtenho desempenho equivalente de qualquer maneira.
Peteter 12/06/19
2
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 offsetvez de offsete offset + 1pode dar algum benefício teórico, mas suspeito que o compilador lida com isso melhor do que eu.
privatestaticreadonlybyte[]LookupTableLow=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticreadonlybyte[]LookupTableHigh=newbyte[]{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,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};privatestaticbyteLookupLow(char c){var b =LookupTableLow[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}privatestaticbyteLookupHigh(char c){var b =LookupTableHigh[c];if(b ==255)thrownewIOException("Expected a hex character, got "+ c);return b;}publicstaticbyteToByte(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.
publicstaticbyte[]FromHexString(string src){if(String.IsNullOrEmpty(src))returnnull;int index = src.Length;int sz = index /2;if(sz <=0)returnnull;byte[] rc =newbyte[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;}
Dois mashups que dobram as duas operações de mordidelas em uma.
Versão provavelmente bastante eficiente:
publicstaticstringByteArrayToString2(byte[] ba){char[] c =newchar[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));}returnnewstring( c );}
Versão linq com hackers decadente:
publicstaticstringByteArrayToString(byte[] ba){returnstring.Concat( ba.SelectMany( b =>newint[]{ b >>4, b &0xF}).Select( b =>(char)(55+ b +(((b-10)>>31)&-7))));}
E inverter:
publicstaticbyte[]HexStringToByteArray(string s ){byte[] ab =newbyte[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;}
HexStringToByteArray ( "09") retorna 0x02 que é ruim
CoperNick
1
Outra maneira é usar stackallocpara reduzir a pressão de memória do GC:
staticstringByteToHexBitFiddle(byte[] bytes){var c =stackallocchar[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';returnnewstring(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 foreachpara 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:
As penalidades para a indexação em uma sequência de caracteres para executar a pesquisa versus uma matriz de caracteres são significativas no teste de arquivos grandes.
As penalidades pelo uso de um StringBuilder de capacidade conhecida versus um array de caracteres de tamanho conhecido para criar a string são ainda mais significativas.
Aqui está o código:
using System;
namespace ConversionExtensions{publicstaticclassByteArrayExtensions{privatereadonlystaticchar[] digits =newchar[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};publicstaticstringToHexString(thisbyte[] bytes){char[] hex =newchar[bytes.Length*2];int index =0;foreach(byte b in bytes){
hex[index++]= digits[b >>4];
hex[index++]= digits[b &0x0F];}returnnewstring(hex);}}}
using System;
using System.IO;
namespace ConversionExtensions{publicstaticclassStringExtensions{publicstaticbyte[]ToBytes(thisstring hexString){if(!string.IsNullOrEmpty(hexString)&& hexString.Length%2!=0){thrownewFormatException("Hexadecimal string must not be empty and must contain an even number of digits to be valid.");}
hexString = hexString.ToUpperInvariant();byte[] data =newbyte[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){thrownewFormatException("An invalid digit was encountered. Valid hexadecimal digits are 0-9 and A-F.");}else{bytevalue=(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.
Respostas:
Ou:
ou:
Existem ainda mais variantes, por exemplo aqui .
A conversão reversa seria assim:
Usar
Substring
é a melhor opção em combinação comConvert.ToByte
. Veja esta resposta para mais informações. Se você precisar de um melhor desempenho, eviteConvert.ToByte
antes que possa cairSubString
.fonte
Análise de desempenho
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 aStringBuilder
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 Legal
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á.
Resultados
unsafe
(via CodesInChaos) (adicionada ao repositório de teste pelo respirador de ar )BitConverter
(via Tomalak){SoapHexBinary}.ToString
(via Mykroft){byte}.ToString("X2")
(usingforeach
) (derivado da resposta de Will Dean){byte}.ToString("X2")
(usando{IEnumerable}.Aggregate
, requer System.Linq) (via Mark)Array.ConvertAll
(usandostring.Join
) (via Will Dean)Array.ConvertAll
(usandostring.Concat
, requer .NET 4.0) (via Will Dean){StringBuilder}.AppendFormat
(usandoforeach
) (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.
Código de teste
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.Atualização (13-01-2010)
Adicionada a resposta de Waleed à análise. Muito rápido.
Atualização (05/10/2011)
string.Concat
Array.ConvertAll
Variante adicionada para completude (requer .NET 4.0). A par dastring.Join
versão.Atualização (05-02-2012)
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, masBitConverter
ainda ganha.Atualização (03/04/2012)
Adicionada a
SoapHexBinary
resposta de Mykroft à análise, que assumiu o terceiro lugar.Atualização (15/01/2013)
Adicionada a resposta de manipulação de bytes do CodesInChaos, que assumiu o primeiro lugar (por uma grande margem em grandes blocos de texto).
Atualização (23-05-2013)
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).
Atualização (31-07-2014)
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.
Atualização (20/08/2015)
Adicionadas otimizações e variantes do respirador de ar ao repositório
unsafe
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.fonte
bytes.ToHexStringAtLudicrousSpeed()
).Há uma classe chamada SoapHexBinary que faz exatamente o que você deseja.
fonte
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.
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn
Uma explicação do pouco estranho:
bytes[i] >> 4
extrai a mordidela alta de um bytebytes[i] & 0xF
extrai a mordidela baixa de um byteb - 10
é
< 0
para valoresb < 10
, que se tornará um dígito decimal,é
>= 0
para valoresb > 10
, que se tornará uma letra deA
paraF
.i >> 31
de um inteiro assinado de 32 bits extrai o sinal, graças à extensão do sinal. Será-1
pori < 0
e0
parai >= 0
.(b-10)>>31
será0
para letras e-1
dígitos.0
eb
está no intervalo de 10 a 15. Queremos mapeá-la paraA
(65) aF
(70), o que implica adicionar 55 ('A'-10
).b
do intervalo de 0 a 9 para o intervalo0
(48) a9
(57). Isso significa que ele precisa se tornar -7 ('0' - 55
).Agora poderíamos multiplicar por 7. Mas como -1 é representado por todos os bits serem 1, podemos usar
& -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 limitesbytes[i]
, então eu escolhi essa variante.b
um int permite conversões desnecessárias de e para byte.fonte
hex string
parabyte[] array
?87 + b + (((b-10)>>31)&-39)
byte[] array
", que literalmente significa uma matriz de matrizes de bytes, oubyte[][]
. 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:Ou, se você estiver usando o .NET 4.0:
(Este último de um comentário na postagem original.)
fonte
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.
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:Ou se você considera aceitável escrever diretamente na string:
fonte
Span
pode ser usado agora em vez deunsafe
??Você pode usar o método BitConverter.ToString:
Resultado:
Mais informações: Método BitConverter.ToString (Byte [])
fonte
Acabei de encontrar o mesmo problema hoje e me deparei com este código:
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).
fonte
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
eString.Substring
se você estiver com pressa ("Código original" abaixo), é a melhor combinação se você não deseja reimplementarConvert.ToByte
. Use algo mais avançado (veja outras respostas) que não será usadoConvert.ToByte
se você precisar de desempenho. Você não usar qualquer outra coisa que não sejaString.Substring
em combinação comConvert.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:
Revisão 4:
A revisão evita
String.Substring
e usa umStringReader
vez disso. O motivo é:Bem, olhando o código de referência para
String.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.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.O que resta é um leitor de strings cujo único "valor" adicionado é um índice paralelo (interno
_pos
) que você poderia ter se declarado (comoj
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 é chamarString.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.Você realmente precisa de um
j
índice que incrementa em etapas de dois paralelos ai
? Claro que não, basta multiplicari
por dois (que o compilador deve poder otimizar para uma adição).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, masString.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 usadoCopyTo
diretamente por lá para evitar a ligação em cima.String.Substring
Método manual
Conclusão? Se você deseja usar
Convert.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
eString.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 completamenteString
).Suspeito que as pessoas que relatam melhor desempenho "evitando
String.Substring
" também evitemConvert.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?
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.
Diverta-se! (Mas otimize com moderação.)
fonte
Complemento para responder por @CodesInChaos (método reverso)
Explicação:
& 0x0f
é apoiar também letras minúsculashi = 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 a0xffffffff & 7
).Para 'A' .. 'F' é
hi = ch - 65 + 10;
(isso é por causa de0x00000000 & 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...
).fonte
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.
Decodificação
Codificação
Comparação
* esta solução
Nota
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.
fonte
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.
fonte
hexString[i] &= ~0x20;
Por que torná-lo complexo? Isso é simples no Visual Studio 2008:
C #:
VB:
fonte
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):
As linhas base64 e 'BitConverter'd' estão lá para testar a correção. Observe que eles são iguais.
A implementação:
Tentei algumas coisas
unsafe
e movi aif
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.)
fonte
Versões seguras:
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.
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).
fonte
Função inversa para o código Waleed Eissa (Hex String para Byte Array):
Função Waleed Eissa com suporte a letras minúsculas:
fonte
Métodos de extensão (exoneração de responsabilidade: código completamente não testado, BTW ...):
etc .. Use uma das três soluções do Tomalak (com a última sendo um método de extensão em uma string).
fonte
Dos desenvolvedores da Microsoft, uma conversão simples e agradável:
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 :
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.
fonte
b.ToSting("X2")
.E para inserir em uma string SQL (se você não estiver usando parâmetros de comando):
fonte
Source == null
ouSource.Length == 0
temos um problema, senhor!Em termos de velocidade, isso parece ser melhor do que qualquer coisa aqui:
fonte
Não recebi o código que você sugeriu para trabalhar, Olipro.
hex[i] + hex[i+1]
aparentemente retornou umint
.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.
fonte
Esta versão do ByteArrayToHexViaByteManipulation pode ser mais rápida.
Dos meus relatórios:
...
E acho que essa é uma otimização:
fonte
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.Convertido do código Java.
fonte
Char[]
e usarChar
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), o
if (b == 255)...
pode ser ignorado e, assim, também o método chama completamente.Usar
offset++
e emoffset
vez deoffset
eoffset + 1
pode dar algum benefício teórico, mas suspeito que o compilador lida com isso melhor do que eu.Isso está no topo da minha cabeça e não foi testado ou comparado.
fonte
Outra variação para a diversidade:
fonte
Não otimizado para velocidade, mas mais LINQy que a maioria das respostas (.NET 4.0):
fonte
Dois mashups que dobram as duas operações de mordidelas em uma.
Versão provavelmente bastante eficiente:
Versão linq com hackers decadente:
E inverter:
fonte
Outra maneira é usar
stackalloc
para reduzir a pressão de memória do GC:fonte
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:
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.
fonte
Outra função rápida ...
fonte