Como você converte uma matriz de bytes em uma sequência hexadecimal e vice-versa?

1372

Como você pode converter uma matriz de bytes em uma seqüência hexadecimal e vice-versa?

alextansc
fonte
8
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:

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.ToByteantes que possa cair SubString.

Tomalak
fonte
24
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á.

Resultados

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.

  1. Adicione o novo método estático ( Func<byte[], string>) a /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Adicione o nome desse método ao TestCandidatesvalor de retorno na mesma classe.
  3. Verifique se você está executando a versão de entrada desejada, frase ou texto, alternando os comentários na GenerateTestInputmesma classe.
  4. Pressione F5e aguarde a saída (um despejo de HTML também é gerado na pasta / bin).
static string ByteArrayToHexStringViaStringJoinArrayConvertAll(byte[] bytes) {
    return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaStringConcatArrayConvertAll(byte[] bytes) {
    return string.Concat(Array.ConvertAll(bytes, b => b.ToString("X2")));
}
static string ByteArrayToHexStringViaBitConverter(byte[] bytes) {
    string hex = BitConverter.ToString(bytes);
    return hex.Replace("-", "");
}
static string ByteArrayToHexStringViaStringBuilderAggregateByteToString(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.Append(b.ToString("X2"))).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachByteToString(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.Append(b.ToString("X2"));
    return hex.ToString();
}
static string ByteArrayToHexStringViaStringBuilderAggregateAppendFormat(byte[] bytes) {
    return bytes.Aggregate(new StringBuilder(bytes.Length * 2), (sb, b) => sb.AppendFormat("{0:X2}", b)).ToString();
}
static string ByteArrayToHexStringViaStringBuilderForEachAppendFormat(byte[] bytes) {
    StringBuilder hex = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes)
        hex.AppendFormat("{0:X2}", b);
    return hex.ToString();
}
static string ByteArrayToHexViaByteManipulation(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    byte b;
    for (int i = 0; i < bytes.Length; i++) {
        b = ((byte)(bytes[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(bytes[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}
static string ByteArrayToHexViaByteManipulation2(byte[] bytes) {
    char[] c = new char[bytes.Length * 2];
    int b;
    for (int i = 0; i < bytes.Length; i++) {
        b = bytes[i] >> 4;
        c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
        b = bytes[i] & 0xF;
        c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
    }
    return new string(c);
}
static string ByteArrayToHexViaSoapHexBinary(byte[] bytes) {
    SoapHexBinary soapHexBinary = new SoapHexBinary(bytes);
    return soapHexBinary.ToString();
}
static string ByteArrayToHexViaLookupAndShift(byte[] bytes) {
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    string hexAlphabet = "0123456789ABCDEF";
    foreach (byte b in bytes) {
        result.Append(hexAlphabet[(int)(b >> 4)]);
        result.Append(hexAlphabet[(int)(b & 0xF)]);
    }
    return result.ToString();
}
static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_Lookup32, GCHandleType.Pinned).AddrOfPinnedObject();
static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes) {
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result) {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++) {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}
static uint[] _Lookup32 = Enumerable.Range(0, 255).Select(i => {
    string s = i.ToString("X2");
    return ((uint)s[0]) + ((uint)s[1] << 16);
}).ToArray();
static string ByteArrayToHexViaLookupPerByte(byte[] bytes) {
    var result = new char[bytes.Length * 2];
    for (int i = 0; i < bytes.Length; i++)
    {
        var val = _Lookup32[bytes[i]];
        result[2*i] = (char)val;
        result[2*i + 1] = (char) (val >> 16);
    }
    return new string(result);
}
static string ByteArrayToHexViaLookup(byte[] bytes) {
    string[] hexStringTable = new string[] {
        "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
        "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
        "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
        "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
        "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
        "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
        "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
        "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
        "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
        "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
        "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
        "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
        "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
        "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
        "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
        "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
    };
    StringBuilder result = new StringBuilder(bytes.Length * 2);
    foreach (byte b in bytes) {
        result.Append(hexStringTable[b]);
    }
    return result.ToString();
}

Atualização (13-01-2010)

Adicionada a resposta de Waleed à análise. Muito rápido.

Atualização (05/10/2011)

string.Concat Array.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.

Patridge
fonte
Você gostaria de testar o código da resposta de Waleed? Parece ser muito rápido. stackoverflow.com/questions/311165/…
Cristian Diaconescu
5
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;

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();
}
Mykroft
fonte
35
O SoapHexBinary está disponível no .NET 1.0 e está no mscorlib. Apesar de seu namespace engraçado, ele faz exatamente o que a pergunta foi feita.
Sly Gryphon
4
Ótima descoberta! Observe que você precisará preencher cadeias de caracteres ímpares com um 0 à esquerda para GetStringToBytes, como a outra solução.
Carter Medlin
Você viu a implementação pensada? A resposta aceita tem um IMHO melhor.
precisa saber é
6
Interessante ver a implementação do Mono aqui: github.com/mono/mono/blob/master/mcs/class/corlib/…
Jeremy
1
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.

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:

  1. bytes[i] >> 4extrai a mordidela alta de um byte
    bytes[i] & 0xFextrai a mordidela baixa de um byte
  2. b - 10
    é < 0para valores b < 10, que se tornará um dígito decimal,
    é >= 0para valores b > 10, que se tornará uma letra de AparaF .
  3. 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 .
  4. Combinando 2) e 3), mostra que (b-10)>>31será 0para letras e-1 dígitos.
  5. 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 ).
  6. 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.
CodesInChaos
fonte
10
E hex stringpara byte[] array?
AaA
15
+1 por citar sua fonte corretamente após invocar esse pouco de magia negra. Todos saudam Cthulhu.
Edward
4
E quanto a string para byte []?
Syaiful Nizam Yahya
9
Agradável! Para aqueles que precisam de saída minúsculas, a expressão obviamente muda para87 + b + (((b-10)>>31)&-39)
Exavier
2
@AaA Você disse " byte[] array", que literalmente significa uma matriz de matrizes de bytes, ou byte[][]. Eu estava apenas brincando.
CoolOppo
97

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

Will Dean
fonte
21
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.

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 unsafeirmã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;
}
CodesInChaos
fonte
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??
Konrad
64

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 [])

Baget
fonte
14
Apenas responde metade da pergunta.
Sly Gryphon
3
Onde está a segunda parte da resposta?
Sawan
56

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

Waleed Eissa
fonte
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 []?
Jjxtra # 15/13
19

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

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

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

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

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

tne
fonte
error: {"Não foi possível encontrar nenhum dígito reconhecível."}
Priya Jagtap
17

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

CoperNick
fonte
16

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

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

Codificação

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

Comparação

StringBuilderToStringFromBytes:   106148
BitConverterToStringFromBytes:     15783
ArrayConvertAllToStringFromBytes:  54290
ByteManipulationToCharArray:        8444
TableBasedToCharArray:              5651 *

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

drphrozen
fonte
2
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.

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;
}
Chris F
fonte
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:

C #:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")
Craig Poulton
fonte
2
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:
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 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.)

Ben Mosher
fonte
1
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:

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

Maratius
fonte
5

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);
    }
Geografia
fonte
4

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

Pure.Krome
fonte
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:

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.

Mark
fonte
2
O iterador não é o principal problema desse código. Você deve avaliar b.ToSting("X2").
dólmen
2

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("-", "");
}
Jack Straw
fonte
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:

  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);
  }
Alexey Borzenkov
fonte
2

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;
}
Fredrik Hu
fonte
2

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

    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);
    }
JoseH
fonte
2

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.

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.

Maarten Bodewes
fonte
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.

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.

ClausAndersen
fonte
1

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;          
}
Stas Makutin
fonte
1

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
MCattle
fonte
1

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;
}
JJJ
fonte
1
HexStringToByteArray ( "09") retorna 0x02 que é ruim
CoperNick
1

Outra maneira é usar stackallocpara 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);
}
Kel
fonte
1

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:

  1. 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.
  2. 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
{
    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


JamieSee
fonte
1

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