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

StackOverflow https://stackoverflow.com/questions/311165

  •  10-07-2019
  •  | 
  •  

Pergunta

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

Foi útil?

Solução

Qualquer:

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 de fazer isso, 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;
}

Usando Substring é a melhor opção em combinação com Convert.ToByte. Ver esta resposta Para maiores informações. Se você precisar de um melhor desempenho, deve evitar Convert.ToByte Antes que você possa cair SubString.

Outras dicas

Análise de desempenho

Nota: Novo líder a partir de 2015-08-20.

Eu corri cada um dos vários métodos de conversão através de algum petróleo bruto Stopwatch Testes de desempenho, uma corrida com uma frase aleatória (n = 61, 1000 iterações) e uma corrida com um texto do projeto 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 carrapatos (10.000 ticks = 1 ms) e todas as notas relativas são comparadas ao [mais lento StringBuilder implementação. Para o código usado, veja abaixo ou o repo de estrutura de teste onde agora mantenho o código para executar isso.

Isenção de responsabilidade

AVISO: Não confie nessas estatísticas para nada concreto; Eles são simplesmente uma amostra de dados de amostra. Se você realmente precisar de desempenho de alto nível, teste esses métodos em um representante do ambiente de suas necessidades de produção com o representante de dados do que você usará.

Resultados

As tabelas de pesquisa assumiram a liderança por manipulação de bytes. Basicamente, há alguma forma de pré -computação no que qualquer mordide ou byte estará em hexadecimal. Então, ao rasgar os dados, basta procurar a próxima parte para ver qual seria a sequência hexadecimal. Esse valor é então adicionado à saída de string resultante 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 experimentá-los em um ambiente de produção. Se você tiver restrições de memória diferentes, pode preferir um método com menos alocações a uma que seria mais rápida, mas consumir mais memória.

Código de teste

Sinta -se à vontade para brincar com o código de teste que usei. Uma versão está incluída aqui, mas fique à vontade para clonar o repo e adicione seus próprios métodos. Envie uma solicitação de tração se encontrar algo interessante ou quiser ajudar a melhorar a estrutura de teste que ela usa.

  1. Adicione o novo método estático (Func<byte[], string>) para /tests/convertbytearraytohexstring/test.cs.
  2. Adicione o nome desse método ao TestCandidates Retorno valor nessa mesma classe.
  3. Verifique se você está executando a versão de entrada que deseja, frase ou texto, alternando os comentários em GenerateTestInput na mesma classe.
  4. Acertar F5 e aguarde a saída (um dump 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 (2010-01-13)

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

Atualização (2011-10-05)

Adicionado string.Concat Array.ConvertAll Variante para a completude (requer .NET 4.0). A par de string.Join versão.

Atualização (2012-02-05)

O repo de teste inclui mais variantes, como StringBuilder.Append(b.ToString("X2")). Ninguém perturbou os resultados. foreach é mais rápido que {IEnumerable}.Aggregate, por exemplo, mas BitConverter ainda vence.

Atualização (2012-04-03)

Adicionado Mykroft's SoapHexBinary Resposta à análise, que assumiu o terceiro lugar.

Atualização (2013-01-15)

A resposta de manipulação de byte de Codesinchaos adicionada, que assumiu o primeiro lugar (por uma grande margem em grandes blocos de texto).

Atualização (2013-05-23)

Acrescentou a resposta de Nathan Moinvaziri e a variante do blog de Brian Lambert. Ambos rapidamente, mas não assumem a liderança na máquina de teste que usei (AMD Phenom 9750).

Atualização (2014-07-31)

Adicionada a nova resposta de pesquisa baseada em bytes do @Codesinchaos. Parece ter assumido a liderança nos testes de sentença e nos testes de texto completo.

Atualização (2015-08-20)

Adicionado Airbreather's otimizações e unsafe variante para isso repo da resposta. Se você quiser jogar no jogo inseguro, poderá obter grandes ganhos de desempenho sobre qualquer um dos principais vencedores anteriores em cordas curtas e textos grandes.

Há uma aula chamada Soaphexbinary Isso faz exatamente o que você deseja.

using System.Runtime.Remoting.Metadata.W3cXsd2001;

public static byte[] GetStringToBytes(string value)
{
    SoapHexBinary shb = SoapHexBinary.Parse(value);
    return shb.Value;
}

public static string GetBytesToString(byte[] value)
{
    SoapHexBinary shb = new SoapHexBinary(value);
    return shb.ToString();
}

Ao escrever código criptográfico, é comum evitar ramificações dependentes de dados e pesquisas de tabela para garantir que o tempo de execução não dependa dos dados, pois o tempo dependente de dados pode levar a ataques de canal lateral.

Também é muito 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 da parte estranha mexendo:

  1. bytes[i] >> 4 Extrai a mordidela alta de um byte
    bytes[i] & 0xF extrai a mordide baixa de um byte
  2. b - 10
    é < 0 para valores b < 10, que se tornará um dígito decimal
    é >= 0 para valores b > 10, que se tornará uma carta de A para F.
  3. Usando i >> 31 Em um número inteiro de 32 bits assinado, extrai o sinal, graças à extensão do sinal. Será -1 por i < 0 e 0 por i >= 0.
  4. Combinando 2) e 3), mostra que (b-10)>>31 vai ser 0 para cartas e -1 para dígitos.
  5. Olhando para o caso de cartas, o último sumão se torna 0, e b está no intervalo de 10 a 15. queremos mapeá -lo para A(65) para F(70), o que implica adicionar 55 ('A'-10).
  6. Olhando para o caso de dígitos, queremos adaptar o último sumão para que ele mapas b da faixa de 0 a 9 ao intervalo 0(48) para 9(57). Isso significa que precisa se tornar -7 ('0' - 55).
    Agora poderíamos apenas multiplicar com 7. Mas como -1 é representado por todos os bits sendo 1, podemos usar & -7 desde (0 & -7) == 0 e (-1 & -7) == -7.

Algumas considerações adicionais:

  • Eu não usei uma segunda variável de loop para indexar em c, uma vez que a medição mostra que calculá -la de i é mais barato.
  • Usando exatamente i < bytes.Length Como o limite superior do loop permite que o jitter elimine os limites bytes[i], então eu escolhi essa variante.
  • Fazer b Um INT permite conversões desnecessárias de e para byte.

Se você quer mais flexibilidade do que BitConverter, mas não quero aqueles loops explícitos desajeitados no estilo dos anos 90, então você pode fazer:

String.Join(String.Empty, Array.ConvertAll(bytes, x => x.ToString("X2")));

Ou, se você estiver usando .NET 4.0:

String.Concat(Array.ConvertAll(bytes, x => x.ToString("X2")));

(Este último de um comentário sobre o post original.)

Outra abordagem baseada na tabela de pesquisa. Este usa apenas uma tabela de pesquisa para cada byte, em vez de uma tabela de pesquisa por mordisca.

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

Eu também testei variantes disso usando ushort, struct{char X1, X2}, struct{byte X1, X2} Na tabela de pesquisa.

Dependendo do alvo de compilação (x86, x64), aqueles tiveram o mesmo desempenho aproximadamente ou foram um pouco mais lentos que essa variante.


E para um desempenho ainda maior, seu unsafe irmão:

private static readonly uint[] _lookup32Unsafe = CreateLookup32Unsafe();
private static readonly uint* _lookup32UnsafeP = (uint*)GCHandle.Alloc(_lookup32Unsafe,GCHandleType.Pinned).AddrOfPinnedObject();

private static uint[] CreateLookup32Unsafe()
{
    var result = new uint[256];
    for (int i = 0; i < 256; i++)
    {
        string s=i.ToString("X2");
        if(BitConverter.IsLittleEndian)
            result[i] = ((uint)s[0]) + ((uint)s[1] << 16);
        else
            result[i] = ((uint)s[1]) + ((uint)s[0] << 16);
    }
    return result;
}

public static string ByteArrayToHexViaLookup32Unsafe(byte[] bytes)
{
    var lookupP = _lookup32UnsafeP;
    var result = new char[bytes.Length * 2];
    fixed(byte* bytesP = bytes)
    fixed (char* resultP = result)
    {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++)
        {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return new string(result);
}

Ou, se você considera aceitável escrever diretamente na string:

public static string ByteArrayToHexViaLookup32UnsafeDirect(byte[] bytes)
{
    var lookupP = _lookup32UnsafeP;
    var result = new string((char)0, bytes.Length * 2);
    fixed (byte* bytesP = bytes)
    fixed (char* resultP = result)
    {
        uint* resultP2 = (uint*)resultP;
        for (int i = 0; i < bytes.Length; i++)
        {
            resultP2[i] = lookupP[bytesP[i]];
        }
    }
    return result;
}

Você pode usar o método BitConverter.ToString:

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

Resultado:

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

Mais Informações: Método BitConverter.ToString (byte [])

Acabei de encontrar o mesmo problema hoje e me deparei com este código:

private static string ByteArrayToHex(byte[] barray)
{
    char[] c = new char[barray.Length * 2];
    byte b;
    for (int i = 0; i < barray.Length; ++i)
    {
        b = ((byte)(barray[i] >> 4));
        c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        b = ((byte)(barray[i] & 0xF));
        c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
    }
    return new string(c);
}

Fonte: Postagem do Fórum Byte [] Array para String Hex (Veja o post de Pzahra). Modifiquei um pouco o código para remover o prefixo 0x.

Fiz alguns testes de desempenho no código e foi quase oito vezes mais rápido do que usar o bitconverter.toString () (o mais rápido de acordo com a postagem de Patridge).

Esse problema também pode ser resolvido usando uma tabela de pesquisa. Isso exigiria uma pequena quantidade de memória estática para o codificador e o decodificador. Este método será rápido:

  • Tabela de codificadores 512 bytes ou 1024 bytes (o dobro do tamanho se for necessário, se for necessário)
  • Decodificador Tabela 256 bytes ou 64 Kib (uma pesquisa única ou uma pesquisa dupla de char)

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

Observação

Durante a decodificação da IOException e IndexOutOfRangeException, poderão ocorrer (se um caractere tiver um valor muito alto> 256). Métodos para de/codificar fluxos ou matrizes devem ser implementados, isso é apenas uma prova de conceito.

Esta é uma resposta para Revisão 4 do Resposta altamente popular de Tomalak (e edições subsequentes).

Vou defender que esta edição está errada e explique por que ela pode ser revertida. Ao longo do caminho, você pode aprender uma coisa ou duas sobre alguns internos e ver mais um exemplo do que realmente é a otimização prematura e como ela pode mordê -lo.

tl; dr: Apenas use Convert.ToByte e String.Substring Se você está com pressa ("código original" abaixo), é a melhor combinação se você não quiser reimplementar Convert.ToByte. Use algo mais avançado (veja outras respostas) que não usa Convert.ToByte se vocês precisar atuação. Fazer não Use qualquer outra coisa que não String.Substring em combinação com Convert.ToByte, a menos que alguém tenha algo interessante a dizer sobre isso nos comentários desta resposta.

aviso: Esta resposta pode se tornar obsoleta E se uma Convert.ToByte(char[], Int32) A sobrecarga é 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 as abordagens de otimização corretamente?". Caso contrário, é muito cedo, espere até que seu projeto esteja mais maduro ou até que você precise do desempenho (se houver uma necessidade real, então você irá faço A Hora). Enquanto isso, faça a coisa mais simples que poderia funcionar.

Código original:

    public static byte[] HexadecimalStringToByteArray_Original(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        for (var i = 0; i < outputLength; i++)
            output[i] = Convert.ToByte(input.Substring(i * 2, 2), 16);
        return output;
    }

Revisão 4:

    public static byte[] HexadecimalStringToByteArray_Rev4(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
                output[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
        }
        return output;
    }

A revisão evita String.Substring e usa a StringReader em vez de. O motivo dado é:

EDIT: Você pode melhorar o desempenho para seqüências longas usando um único analisador de passe, assim:

Bem, olhando para o Código de referência para String.Substring, já está claramente "passa única"; E por que não deveria ser? Opera no nível de byte, não em pares substitutos.

No entanto, ele aloca uma nova string, mas você precisa alocar alguém para passar para Convert.ToByte de qualquer forma. Além disso, a solução fornecida na revisão aloca mais um objeto em todas as iterações (a matriz de duas cargas); Você pode colocar essa alocação com segurança fora do loop e reutilizar a matriz para evitar isso.

    public static byte[] HexadecimalStringToByteArray(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        using (var sr = new StringReader(input))
        {
            for (var i = 0; i < outputLength; i++)
            {
                numeral[0] = (char)sr.Read();
                numeral[1] = (char)sr.Read();
                output[i] = Convert.ToByte(new string(numeral), 16);
            }
        }
        return output;
    }

Cada hexadecimal numeral representa um único octeto usando dois dígitos (símbolos).

Mas então, por que ligar StringReader.Read duas vezes? Basta ligar para sua segunda sobrecarga e pedir que leia dois caracteres na matriz de dois char de uma só vez; 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 você resta é um leitor de string cujo único "valor" agregado é um índice paralelo (interno _pos) que você poderia ter se declarado (como j por exemplo), uma variável de comprimento redundante (interno _length) e uma referência redundante à sequência de entrada (interna _s). Em outras palavras, é inútil.

Se você se pergunta como Read "Reads", basta olhar para o código, tudo o que faz é ligar String.CopyTo Na sequência de entrada. O resto é apenas uma sobrecarga de manutenção de livros para manter os valores de que não precisamos.

Então, remova o leitor de cordas e ligue CopyTo você mesma; É 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 incrementos nas etapas de duas paralelas a i? Claro que não, apenas multiplique i por dois (que o compilador deve ser capaz de otimizar para uma adição).

    public static byte[] HexadecimalStringToByteArray_BestEffort(string input)
    {
        var outputLength = input.Length / 2;
        var output = new byte[outputLength];
        var numeral = new char[2];
        for (int i = 0; i < outputLength; i++)
        {
            input.CopyTo(i * 2, numeral, 0, 2);
            output[i] = Convert.ToByte(new string(numeral), 16);
        }
        return output;
    }

Como é a solução agora? Exatamente como era no começo, apenas em vez de usar String.Substring Para alocar a string e copiar os dados, você está usando uma matriz intermediária para a qual copia os números hexadecimais, depois aloce a string e copie os dados novamente da matriz e para a string (quando você a passa no construtor de string). A segunda cópia pode ser otimizada se a string já estiver no pool interno, mas depois String.Substring Também será capaz de evitá -lo nesses casos.

De fato, se você olhar para String.Substring Novamente, você vê que ele usa algum conhecimento interno de baixo nível de como as strings são construídas para alocar a string mais rápida do que você normalmente poderia fazê-lo, e ela inline o mesmo código usado por CopyTo diretamente lá para evitar a chamada de chamada.

String.Substring

  • Pior caso: uma alocação rápida, uma cópia rápida.
  • Melhor caso: sem alocação, sem cópia.

Método manual

  • Pior caso: duas alocações normais, uma cópia normal, uma cópia rápida.
  • Melhor caso: uma alocação normal, uma cópia normal.

Conclusão? Se você quiser usar Convert.ToByte(String, Int32) (Porque você não quer reimplementar essa funcionalidade), não parece haver uma maneira de vencer String.Substring; Tudo o que você faz é ser executado em círculos, reinventando a roda (apenas com materiais abaixo do ideal).

Observe isso usando Convert.ToByte e String.Substring é uma escolha perfeitamente válida se você não precisar de desempenho extremo. Lembre -se: apenas opte por uma alternativa se você tiver tempo e recursos para investigar como ele 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).

Eu suspeito que as pessoas que relatam melhor desempenho "evitando String.Substring"Evite também Convert.ToByte(String, Int32), o que você realmente deve fazer se precisar do desempenho de qualquer maneira. Veja as inúmeras outras respostas para descobrir todas as diferentes abordagens para fazer isso.

Isenção de responsabilidade: não descompinei a versão mais recente da estrutura para verificar se a fonte de referência está atualizada, presumo que seja.

Agora, tudo parece bom e lógico, espero até óbvio se você conseguiu chegar tão longe. Mas isso é 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 a Partridge para a estrutura do banco, é fácil invadir. A entrada usada é o seguinte hash sha-1 repetido 5000 vezes para fazer uma corda de 100.000 bytes de comprimento.

209113288F93A9AB8E474EA78D899AFDBB874355

Divirta-se! (Mas otimize com moderação.)

Complemento para responder por @codesinchaos (método revertido)

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 é suportar 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 que hi = ch - 65 + 10 + 7; qual é hi = ch - 48 (Isso é por causa de 0xffffffff & 7).

Para 'a' .. 'f' é hi = ch - 65 + 10; (Isso é por causa de 0x00000000 & 7).

Para 'a' .. 'f', temos que grandes números, então devemos subtrair 32 da versão padrão fazendo alguns bits 0 usando & 0x0f.

65 é o código para 'A'

48 é código para '0'

7 é o número de letras entre '9' e 'A' na tabela ASCII (...456789:;<=>?@ABCD...).

Este é um ótimo post. Eu gosto da solução de Waleed. Eu não passei pelo teste de Patridge, mas parece ser muito rápido. Eu também precisava do processo reverso, convertendo uma corda 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 de Tomalak. Novamente, também não executei o processo reverso através do 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;
}

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("-", "")

Não para acumular as muitas respostas aqui, mas achei um ótimo (~ 4,5x melhor do que aceito), implementação direta do analisador de cordas hexadecipal. Primeiro, a 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;
}

Eu tentei algumas coisas com unsafe e mover o personagem (claramente redundante) if Sequência de outro método, mas esse foi o mais rápido que obteve.

(Eu admiti que isso responde metade da pergunta. Senti que a conversão de string-> byte [] estava sub-representada, enquanto o byte []-> ângulo de string parece estar bem coberto. Assim, esta resposta.)

Versões seguras:

public static class HexHelper
{
    [System.Diagnostics.Contracts.Pure]
    public static string ToHex(this byte[] value)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        const string hexAlphabet = @"0123456789ABCDEF";

        var chars = new char[checked(value.Length * 2)];
        unchecked
        {
            for (int i = 0; i < value.Length; i++)
            {
                chars[i * 2] = hexAlphabet[value[i] >> 4];
                chars[i * 2 + 1] = hexAlphabet[value[i] & 0xF];
            }
        }
        return new string(chars);
    }

    [System.Diagnostics.Contracts.Pure]
    public static byte[] FromHex(this string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (value.Length % 2 != 0)
            throw new ArgumentException("Hexadecimal value length must be even.", "value");

        unchecked
        {
            byte[] result = new byte[value.Length / 2];
            for (int i = 0; i < result.Length; i++)
            {
                // 0(48) - 9(57) -> 0 - 9
                // A(65) - F(70) -> 10 - 15
                int b = value[i * 2]; // High 4 bits.
                int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
                b = value[i * 2 + 1]; // Low 4 bits.
                val += (b - '0') + ((('9' - b) >> 31) & -7);
                result[i] = checked((byte)val);
            }
            return result;
        }
    }
}

Versões inseguras Para aqueles que preferem desempenho e não têm medo de insegura. Cerca de 35% mais rápido e 10% mais rápido daHex.

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

POR FALAR NISSOPara testes de referência inicializando o alfabeto sempre que a função convertida chamada está errada, o alfabeto deve ser const (para string) ou estático readonly (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 lançamento (com otimização) e com a opção de depuração "suprimir a otimização do jit" desligada (o mesmo para "Ativar apenas meu código" se o código for debiltável).

Função inversa para o código Eissa Waleed (string hexadecida para matriz de bytes):

    public static byte[] HexToBytes(this string hexString)        
    {
        byte[] b = new byte[hexString.Length / 2];            
        char c;
        for (int i = 0; i < hexString.Length / 2; i++)
        {
            c = hexString[i * 2];
            b[i] = (byte)((c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57)) << 4);
            c = hexString[i * 2 + 1];
            b[i] += (byte)(c < 0x40 ? c - 0x30 : (c < 0x47 ? c - 0x37 : c - 0x57));
        }

        return b;
    }

Waleed Eissa Função com Suporte a minúsculas:

    public static string BytesToHex(this byte[] barray, bool toLowerCase = true)
    {
        byte addByte = 0x37;
        if (toLowerCase) addByte = 0x57;
        char[] c = new char[barray.Length * 2];
        byte b;
        for (int i = 0; i < barray.Length; ++i)
        {
            b = ((byte)(barray[i] >> 4));
            c[i * 2] = (char)(b > 9 ? b + addByte : b + 0x30);
            b = ((byte)(barray[i] & 0xF));
            c[i * 2 + 1] = (char)(b > 9 ? b + addByte : b + 0x30);
        }

        return new string(c);
    }

Métodos de extensão (Isençã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 qualquer um de Três soluções de Tomalak (com o último sendo um método de extensão em uma string).

Dos desenvolvedores da Microsoft, uma conversão agradável e simples:

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 um 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 de Tomolak:

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   

   for(int i=0; i < ga.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 aceite apenas minha palavra ... teste de desempenho a cada rotina e inspecione seu código CIL para si mesmo.

Em termos de velocidade, isso parece ser melhor do que qualquer coisa aqui:

  public static string ToHexString(byte[] data) {
    byte b;
    int i, j, k;
    int l = data.Length;
    char[] r = new char[l * 2];
    for (i = 0, j = 0; i < l; ++i) {
      b = data[i];
      k = b >> 4;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
      k = b & 15;
      r[j++] = (char)(k > 9 ? k + 0x37 : k + 0x30);
    }
    return new string(r);
  }

Não recebi o código que você sugeriu para trabalhar, Olipro. hex[i] + hex[i+1] aparentemente devolveu um int.

No entanto, tive algum sucesso ao receber algumas dicas do Waleeds Code e martelando isso junto. É feio como o inferno, mas parece funcionar e se apresenta 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. Alternando o ?: S para separar o 0-9 primeiro provavelmente produziria um resultado um pouco mais rápido, pois há mais números do que letras.

public static byte[] StringToByteArray2(string hex)
{
    byte[] bytes = new byte[hex.Length/2];
    int bl = bytes.Length;
    for (int i = 0; i < bl; ++i)
    {
        bytes[i] = (byte)((hex[2 * i] > 'F' ? hex[2 * i] - 0x57 : hex[2 * i] > '9' ? hex[2 * i] - 0x37 : hex[2 * i] - 0x30) << 4);
        bytes[i] |= (byte)(hex[2 * i + 1] > 'F' ? hex[2 * i + 1] - 0x57 : hex[2 * i + 1] > '9' ? hex[2 * i + 1] - 0x37 : hex[2 * i + 1] - 0x30);
    }
    return bytes;
}

Esta versão do BytearraytoHexviByTemanipulation pode ser mais rápida.

Dos meus relatórios:

  • BytearraytoHexviByTemanipulation3: 1,68 ticks médios (mais de 1000 corridas), 17,5x
  • BytearraytohexviByTemanipulation2: 1,73 ticks médios (mais de 1000 corridas), 16,9x
  • BytearraytoHexviByTemanipulation: 2,90 ticks médios (mais de 1000 corridas), 10,1x
  • BytearraytoHexVialookupandShift: 3,22 ticks médios (mais de 1000 corridas), 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 eu acho que este é 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);
    }

Vou participar desta competição bitdling, pois tenho uma resposta que também usa bits para decodificar hexadecimals. Observe que o uso de matrizes de caracteres pode ser ainda mais rápido como chamado StringBuilder Os métodos 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.

Para desempenho, eu iria com a solução DrPhrozens. Uma minúscula otimização para o decodificador pode ser usar uma tabela para qualquer char se livrar do "<< 4".

Claramente, as duas chamadas de método são caras. Se algum tipo de verificação for feito nos dados de entrada ou saída (pode ser CRC, soma de verificação ou qualquer outra coisa) if (b == 255)... poderia ser ignorado e, assim, também o método chama completamente.

Usando offset++ e offset ao invés de offset e offset + 1 Pode dar algum benefício teórico, mas suspeito que o compilador lida com isso melhor do que eu.

private static readonly byte[] LookupTableLow = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static readonly byte[] LookupTableHigh = new byte[] {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

private static byte LookupLow(char c)
{
  var b = LookupTableLow[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

private static byte LookupHigh(char c)
{
  var b = LookupTableHigh[c];
  if (b == 255)
    throw new IOException("Expected a hex character, got " + c);
  return b;
}

public static byte ToByte(char[] chars, int offset)
{
  return (byte)(LookupHigh(chars[offset++]) | LookupLow(chars[offset]));
}

Isso está fora do topo da minha cabeça e não foi testado ou comparado.

E para inserir 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("-", "");
}

Mais uma variação para a diversidade:

public static byte[] FromHexString(string src)
{
    if (String.IsNullOrEmpty(src))
        return null;

    int index = src.Length;
    int sz = index / 2;
    if (sz <= 0)
        return null;

    byte[] rc = new byte[sz];

    while (--sz >= 0)
    {
        char lo = src[--index];
        char hi = src[--index];

        rc[sz] = (byte)(
            (
                (hi >= '0' && hi <= '9') ? hi - '0' :
                (hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
                (hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
                0
            )
            << 4 | 
            (
                (lo >= '0' && lo <= '9') ? lo - '0' :
                (lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
                (lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
                0
            )
        );
    }

    return rc;          
}

Não otimizado para velocidade, mas mais linqy do que a maioria das respostas (.NET 4.0):

<Extension()>
Public Function FromHexToByteArray(hex As String) As Byte()
    hex = If(hex, String.Empty)
    If hex.Length Mod 2 = 1 Then hex = "0" & hex
    Return Enumerable.Range(0, hex.Length \ 2).Select(Function(i) Convert.ToByte(hex.Substring(i * 2, 2), 16)).ToArray
End Function

<Extension()>
Public Function ToHexString(bytes As IEnumerable(Of Byte)) As String
    Return String.Concat(bytes.Select(Function(b) b.ToString("X2")))
End Function

Dois mashups que dobram as duas operações de mordidela em uma.

Provavelmente uma versão 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 decadente de Linq-With-bit-bit:

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

public static byte[] HexStringToByteArray( string s )
{
    byte[] ab = new byte[s.Length>>1];
    for( int i = 0; i < s.Length; i++ )
    {
        int b = s[i];
        b = (b - '0') + ((('9' - b)>>31)&-7);
        ab[i>>1] |= (byte)(b << 4*((i&1)^1));
    }
    return ab;
}

Outra maneira é usar stackalloc Para reduzir a pressão da memória GC:

static string ByteToHexBitFiddle(byte[] bytes)
{
        var c = stackalloc char[bytes.Length * 2 + 1];
        int b; 
        for (int i = 0; i < bytes.Length; ++i)
        {
            b = bytes[i] >> 4;
            c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7));
            b = bytes[i] & 0xF;
            c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7));
        }
        c[bytes.Length * 2 ] = '\0';
        return new string(c);
}

Aqui está minha chance disso. Eu criei um par de classes de extensão para estender string e byte. No teste de arquivo grande, o desempenho é comparável à manipulação de bytes 2.

O código abaixo para o ToHexString é uma implementação otimizada do algoritmo de pesquisa e mudança. É quase idêntico ao de Behrooz, mas acaba usando um foreach para iterar e um contador é mais rápido que uma indexação explicitamente for.

Ele vem em 2º lugar atrás da manipulação de byte 2 na minha máquina e é um código muito legível. Os seguintes resultados dos testes também são de interesse:

ToHexStringCharArrayWithCharArrayLookup: 41.589,69 Ticks médios (mais de 1000 corridas), 1,5x ToHexStringCharArraywithStringLook: 50.764.06 Ticks médios (mais 1000 corridas), 1.2x ToHexstringBuilderWithCharArrayLook: 6000), 1000, 1,2 x 1x.

Com base nos resultados acima, parece seguro concluir que:

  1. As penalidades por indexação em uma string para executar a pesquisa vs. uma matriz de char são significativas no teste de arquivo grande.
  2. As penalidades por usar um StringBuilder de capacidade conhecida versus uma matriz de char de tamanho conhecida 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 da @Patridge na minha máquina. Também adicionei um teste para converter para uma matriz de bytes da Hexadecimal. Os testes que exercitaram meu código são bytearraytohexvia otimized llowupa e oShift e HextobyteArrayviByTemanipulation. O Hextobytearrayviaconverttobyte foi retirado de xxxx. O Hextobytearrayvisoaphexbinary é o da resposta de @mykroft.

Processador Xeon Intel Pentium III

    Cores: 4 <br/>
    Current Clock Speed: 1576 <br/>
    Max Clock Speed: 3092 <br/>

Convertendo matriz de bytes em representação de string hexadecimal


BytearraytoHexviByTemanipulation2: 39.366,64 Ticks médios (mais de 1000 corridas), 22,4x

BytearraytoHexviaOptimizedLeovesChift: 41.588,64 ticks médios (mais de 1000 corridas), 21,2x

BytearraytoHexVialookup: 55.509,56 ticks médios (mais de 1000 corridas), 15,9x

BytearraytoHexviByTemanipulation: 65.349,12 Ticks médios (mais de 1000 corridas), 13,5x

BytearraytoHexVialookupandShift: 86.926,87 ticks médios (mais de 1000 corridas), 10.2x

ByteArraytoHexStringViAbitConverter: 139.353,73 Ticks médios (mais de 1000 corridas), 6,3x

BytearraytoHexvisoApHexbinary: 314.598,77 Ticks médios (mais de 1000 corridas), 2,8x

ByteArraytoHexStringViastringBuilderForEachbytetoString: 344.264.63 Ticks médios (mais de 1000 corridas), 2.6x

BytearraytoHexStringViastringBuilderAggregateBytetoString: 382.623.44 Ticks médios (mais de 1000 corridas), 2.3x

ByteArraytoHexStringViastringBuilderForAppendFormat: 818.111,95 ticks médios (mais de 1000 corridas), 1,1x

ByteArraytoHexStringViastringConCatArrayConvertall: 839.244.84 Ticks médios (mais de 1000 corridas), 1,1x

ByteArraytoHexStringViastringBuilderAggregateAppendFormat: 867.303.98 Ticks médios (mais de 1000 corridas), 1.0x

ByteArraytoHexStringViastringJaNarrayConvertall: 882.710,28 ticks médios (mais de 1000 corridas), 1,0x


Outra função rápida ...

private static readonly byte[] HexNibble = new byte[] {
    0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
    0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
    0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
};

public static byte[] HexStringToByteArray( string str )
{
    int byteCount = str.Length >> 1;
    byte[] result = new byte[byteCount + (str.Length & 1)];
    for( int i = 0; i < byteCount; i++ )
        result[i] = (byte) (HexNibble[str[i << 1] - 48] << 4 | HexNibble[str[(i << 1) + 1] - 48]);
    if( (str.Length & 1) != 0 )
        result[byteCount] = (byte) HexNibble[str[str.Length - 1] - 48];
    return result;
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top