representation - hexstring c#




Como você converte uma matriz de bytes em uma string hexadecimal e vice-versa? (20)

Análise de desempenho

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

Eu executei cada um dos vários métodos de conversão através de alguns testes crus de desempenho do Stopwatch , 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 ao mais lento. Todas as medidas estão em ticks ( 10.000 ticks = 1 ms ) e todas as notas relativas são comparadas à implementação [ StringBuilder ] [mais lenta]. Para o código usado, veja abaixo ou o repositório de estrutura de teste, onde agora eu 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 um desempenho de alto nível, por favor, teste estes métodos em um ambiente representativo de suas necessidades de produção com dados representativos do que você irá usar.

Resultados

  • Pesquisa por byte unsafe (via CodesInChaos) (adicionado para testar o repo pelo airbreather )
    • Texto: 4.727,85 (105,2X)
    • Sentença: 0,28 (99,7X)
  • Pesquisa por byte (via CodesInChaos)
    • Texto: 10.853,96 (45,8 vezes mais rápido)
    • Sentença: 0,65 (42,7X mais rápido)
  • Manipulação de Byte 2 (via CodesInChaos)
    • Texto: 12.967,69 (38,4 vezes mais rápido)
    • Sentença: 0,73 (37,9 vezes mais rápido)
  • Manipulação de Byte (via Waleed Eissa)
    • Texto: 16.856,64 (29,5 vezes mais rápido)
    • Sentença: 0,70 (39,5 vezes mais rápido)
  • Lookup / Shift (via Nathan Moinvaziri)
    • Texto: 23.201.23 (21.4X mais rápido)
    • Sentença: 1,24 (22,3 vezes mais rápido)
  • Pesquisa por mordidela (via Brian Lambert)
    • Texto: 23,879.41 (20,8X mais rápido)
    • Sentença: 1,15 (23,9 vezes mais rápido)
  • BitConverter (via Tomalak)
    • Texto: 113,269.34 (4,4X mais rápido)
    • Sentença: 9,98 (2,8 vezes mais rápido)
  • {SoapHexBinary}.ToString (via Mykroft)
    • Texto: 178,601.39 (2.8X mais rápido)
    • Sentença: 10,68 (2,6 vezes mais rápido)
  • {byte}.ToString("X2") (usando foreach ) (derivado da resposta de Will Dean)
    • Texto: 308.805,38 (2,4 vezes mais rápido)
    • Sentença: 16,89 (2,4 vezes mais rápido)
  • {byte}.ToString("X2") (usando {IEnumerable}.Aggregate , requer System.Linq) (via Mark)
    • Texto: 352.828,20 (2.1x mais rápido)
    • Sentença: 16,87 (2,4 vezes mais rápido)
  • Array.ConvertAll (usando string.Join ) (via Will Dean)
    • Texto: 675.451,57 (1,1 vezes mais rápido)
    • Sentença: 17,95 (2,2 vezes mais rápido)
  • Array.ConvertAll (usando string.Concat , requer .NET 4.0) (via Will Dean)
    • Texto: 752.078,70 (1,0 X mais rápido)
    • Sentença: 18,28 (2,2 vezes mais rápido)
  • {StringBuilder}.AppendFormat (usando foreach ) (via Tomalak)
    • Texto: 672.115,77 (1,1 vezes mais rápido)
    • Sentença: 36,82 (1,1X mais rápido)
  • {StringBuilder}.AppendFormat (usando {IEnumerable}.Aggregate , requer System.Linq) (derivado da resposta do Tomalak)
    • Texto: 718,380.63 (1,0 X mais rápido)
    • Sentença: 39,71 (1,0X mais rápido)

As tabelas de pesquisa tomaram a liderança sobre a manipulação de bytes. Basicamente, existe alguma forma de pré-computar o que qualquer dado nibble ou byte será em hexadecimal. Então, conforme você rasga os dados, basta procurar a próxima parte para ver qual seqüência hexadecimal seria. Esse valor é então adicionado à saída da string resultante de alguma forma. Por muito 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 de produção. Se você tiver restrições de memória diferentes, talvez prefira um método com menos alocações para uma que seja mais rápida, mas consuma 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 repositório e adicionar seus próprios métodos. Por favor, envie uma solicitação pull se encontrar algo interessante ou quiser ajudar a melhorar a estrutura de testes que usa.

  1. Adicione o novo método estático ( Func<byte[], string> ) a /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Adicione o nome desse método ao valor de retorno TestCandidates nessa mesma classe.
  3. Certifique-se de estar executando a versão de entrada desejada, frase ou texto, alternando os comentários em GenerateTestInput nessa mesma classe.
  4. Pressione 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)

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

Atualização (2011-10-05)

Adicionada a variante string.Concat Array.ConvertAll (requer o .NET 4.0). Em par com string.Join versão.

Atualização (2012-02-05)

O StringBuilder.Append(b.ToString("X2")) teste inclui mais variantes, como StringBuilder.Append(b.ToString("X2")) . Nenhum aborreceu os resultados algum. foreach é mais rápido que {IEnumerable}.Aggregate , por exemplo, mas BitConverter ainda ganha.

Atualização (2012-04-03)

Adicionada a resposta da SokHexBinary da SoapHexBinary à análise, que ocupou o terceiro lugar.

Atualização (2013-01-15)

Adicionado 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 (2013-05-23)

Adicionado resposta de pesquisa de Nathan Moinvaziri e a variante do blog de Brian Lambert. Ambos bastante rápido, mas não assumindo a liderança na máquina de teste que usei (AMD Phenom 9750).

Atualização (2014-07-31)

Adicionado nova resposta de pesquisa baseada em byte do @CodesInChaos. Parece ter assumido a liderança tanto nos testes de sentença quanto nos testes de texto completo.

Atualização (2015-08-20)

Adicionado otimizações airbreather e variante unsafe ao repo desta resposta . Se você quiser jogar no jogo inseguro, você pode obter grandes ganhos de desempenho sobre qualquer um dos vencedores anteriores em ambas as seqüências curtas e grandes textos.

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


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: Fórum post byte [] Array para Hex String (veja o post de PZahra). Eu modifiquei o código um pouco para remover o prefixo 0x.

Eu fiz alguns testes de desempenho para o código e foi quase oito vezes mais rápido do que usando BitConverter.ToString () (o mais rápido de acordo com o post do patridge).


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 do codificador 512 bytes ou 1024 bytes (o dobro do tamanho, se for necessário maiúsculas e minúsculas)
  • Tabela decodificadora de 256 bytes ou 64 KiB (ou um único look de char ou dual char look-up)

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 de / codificação de fluxos ou matrizes devem ser implementados, isto é apenas uma prova de conceito.


Esta é uma resposta à revisão 4 da share de share (e edições subsequentes).

Vou argumentar que essa 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 a otimização prematura realmente é e como ela pode afetá-lo.

tl; dr: Apenas use Convert.ToByte e String.Substring se você estiver com pressa ("Código original" abaixo), é a melhor combinação se você não quiser reimplantar o Convert.ToByte . Use algo mais avançado (veja outras respostas) que não use Convert.ToByte se você precisar de desempenho. Não use nada além de 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 se uma sobrecarga Convert.ToByte(char[], Int32) for implementada na estrutura. É improvável que isso aconteça em breve.

Como regra geral, não gosto muito de dizer "não otimizar prematuramente", porque ninguém sabe quando é "prematuro". A única coisa que você deve considerar ao decidir se deseja otimizar ou não é: "Eu tenho tempo e recursos para investigar as abordagens de otimização corretamente?". Se você não o fizer, então é muito cedo, espere até que seu projeto esteja mais maduro ou até que você precise do desempenho (se houver uma necessidade real, você fará o tempo). Enquanto isso, faça a coisa mais simples que poderia funcionar em seu lugar.

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 um StringReader . A razão dada é:

Edit: você pode melhorar o desempenho de strings longas usando um analisador de passagem única, da seguinte forma:

Bem, olhando para o código de referência para String.Substring , é claramente "single-pass" já; e por que não deveria ser? Ele opera em nível de byte, não em pares substitutos.

No entanto, você aloca uma nova string, mas precisa alocar uma para passar para o Convert.ToByte . Além disso, a solução fornecida na revisão aloca ainda 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 de uma só vez; e reduzir a quantidade de chamadas por 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 declarado (como jpor exemplo), uma variável de comprimento redundante (interna _length) e uma referência redundante à entrada string (interno _s). Em outras palavras, é inútil.

Se você quer saber como Read"lê", basta olhar o código , tudo o que ele faz é chamar String.CopyToa string de entrada. O resto é apenas uma sobrecarga de manutenção de livros para manter valores que não precisamos.

Então, remova o leitor de string já e chame CopyTovocê 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 seja incrementado em etapas de duas paralelas i? Claro que não, basta multiplicar ipor 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 início, só que ao invés de usar String.Substringpara alocar a corda e copiar os dados a ele, você está usando uma matriz intermediário para que você copiar os números hexadecimais para, em seguida, alocar a string-se e copiar os dados novamente a partir o array e na string (quando você passa no construtor de string). A segunda cópia pode ser otimizada se a string já estiver no pool interno, mas String.Substringtambém poderá evitá-la nesses casos.

Na verdade, se você observar String.Substringnovamente, verá que ele usa um conhecimento interno de baixo nível de como as cadeias são construídas para alocar a cadeia de caracteres mais rapidamente do que você normalmente faria, e insere o mesmo código usado CopyTodiretamente nela para evitar a sobrecarga de chamadas.

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 usarConvert.ToByte(String, Int32) (porque você não quer reimplementar essa funcionalidade), não parece haver uma maneira de vencer String.Substring; tudo que você faz é correr em círculos, reinventando a roda (somente com materiais abaixo do ideal).

Note que usar Convert.ToBytee String.Substringé uma escolha perfeitamente válida se você não precisa de desempenho extremo. Lembre-se: só opte por uma alternativa se você tiver tempo e recursos para investigar como ela funciona corretamente.

Se houvesse 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 reportam melhor desempenho evitando, String.Substringtambém evitem Convert.ToByte(String, Int32), o que você realmente deveria estar fazendo se precisasse do desempenho de qualquer maneira. Veja as inúmeras outras respostas para descobrir todas as diferentes abordagens para fazer isso.

Disclaimer: Eu não descompilei 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 que seja ó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!

Props para Partridge para o framework de bancada, é fácil hackear. A entrada usada é o seguinte SHA-1 hash repetido 5000 vezes para fazer uma cadeia de 100.000 bytes.

209113288F93A9AB8E474EA78D899AFDBB874355

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


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

Há ainda mais variantes de fazer isso, por exemplo, here .

A conversão inversa 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 . Veja esta resposta para mais informações. Se você precisa de um melhor desempenho, você deve evitar o Convert.ToByte antes de poder descartar o SubString .


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

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 deste usando ushort , struct{char X1, X2} , struct{byte X1, X2} na tabela de pesquisa.

Dependendo do destino de compilação (x86, X64), esses tiveram o mesmo desempenho ou foram ligeiramente mais lentos que essa variante.

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

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

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

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

Ou se você considerar 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));

Saída:

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

Mais informações: Método BitConverter.ToString (Byte [])


Métodos de extensão (disclaimer: 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();
    }
}

Use uma das três soluções da Tomalak (com a última sendo um método de extensão em uma string).


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

Complemento para responder por @CodesInChaos (método invertido)

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(isto é devido a 0xffffffff & 7).

Para 'A' .. 'F' é hi = ch - 65 + 10;(isso é por causa de 0x00000000 & 7).

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

65 é código para 'A'

48 é o código para '0'

7 é o número de letras entre '9'e 'A'na tabela ASCII ( ...456789:;<=>[email protected]).


Dos desenvolvedores da Microsoft, uma boa conversão 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, viciados em desempenho vai gritar sobre isso usando enumeradores. Você pode obter o melhor desempenho com uma versão melhorada 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 vi postadas aqui até agora. Não tome apenas minha palavra ... teste de desempenho em cada rotina e inspecione seu código CIL por si mesmo.


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

Esta versão do ByteArrayToHexViaByteManipulation poderia ser mais rápida.

Dos meus relatórios:

  • ByteArrayToHexViaByteManipulation3: 1,68 ticks médios (mais de 1.000 execuções), 17,5 vezes
  • ByteArrayToHexViaByteManipulation2: 1,73 ticks médios (mais de 1.000 execuções), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 ticks médios (mais de 1.000 execuções), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 ticks médios (mais de 1.000 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 eu acho que esse é 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);
    }

Função inversa para o código Waleed Eissa (Hex String To 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 de 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);
    }

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

Aqui está a minha chance. 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 ToHexString é uma implementação otimizada do algoritmo de lookup e shift. É quase idêntico ao de Behrooz, mas acontece usar um foreachpara iterar e um contador é mais rápido que uma indexação explícita for.

Ele vem em segundo lugar atrás de Byte Manipulation 2 na minha máquina e é um código muito legível. Os seguintes resultados do teste também são de interesse:

ToHexStringCharArrayWithCharArrayLookup: 41,589.69 tiques médios (mais de 1.000 execuções), 1.5X ToHexStringCharArrayWithStringLookup: 50.764.06 tiques médios (mais de 1.000 execuções), 1.2X ToHexStringStringBuilderWithCharArrayLookup: 62.812,87 tiques médios (acima de 1.000 execuções), 1.0X

Com base nos resultados acima, parece seguro concluir que:

  1. As penalidades para indexação em uma string para executar a pesquisa em relação a uma matriz char são significativas no teste de arquivo grande.
  2. As penalidades para usar um StringBuilder de capacidade conhecida em relação a uma matriz char 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 do teste que recebi quando coloquei meu código no projeto de testes do @patridge na minha máquina. Eu também adicionei um teste para converter em uma matriz de bytes de hexadecimal. As execuções de teste que exerceram meu código são ByteArrayToHexViaOptimizedLookupAndShift e HexToByteArrayViaByteManipulation. O HexToByteArrayViaConvertToByte foi retirado de XXXX. O HexToByteArrayViaSoapHexBinary é o da resposta do @ Mykroft.

Processador Intel Pentium III Xeon

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

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

ByteArrayToHexViaByteManipulation2: 39,366.64 ticks médios (mais de 1.000 execuções), 22.4X

ByteArrayToHexViaOptimizedLookupAndShift: 41.588,64 ticks médios (mais de 1.000 execuções), 21,2 vezes

ByteArrayToHexViaLookup: 55,509.56 ticks médios (mais de 1.000 execuções), 15,9x

ByteArrayToHexViaByteManipulation: 65.349.12 ticks médios (mais de 1.000 execuções), 13.5X

ByteArrayToHexViaLookupAndShift: 86.926,87 ticks médios (mais de 1.000 execuções), 10.2X

ByteArrayToHexStringViaBitConverter: 139,353.73 ticks médios (mais de 1000 execuções), 6,3 vezes

ByteArrayToHexViaSoapHexBinary: 314,598.77 ticks médios (mais de 1.000 execuções), 2.8X

ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264.63 tiques médios (acima de 1000 execuções), 2,6 vezes

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382,623.44 ticks médios (mais de 1.000 execuções), 2.3X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95 tiques médios (acima de 1.000 execuções), 1,1X

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 ticks médios (mais de 1.000 execuções), 1.1X

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867,303.98 tiques médios (acima de 1.000 execuções), 1.0X

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882.710,28 pulsos médios (acima de 1.000 execuções), 1.0X


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

Eu fiz, no entanto, ter algum sucesso, tomando algumas dicas do código Waleeds e martelando isso juntos. É feio pra caramba, mas parece funcionar e funciona em 1/3 do tempo comparado com os outros de acordo com meus testes (usando o mecanismo de teste de patridges). Dependendo do tamanho da entrada. Mudar ao redor de?: S para separar 0-9 primeiro provavelmente renderia um resultado ligeiramente mais rápido, já que há mais números 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;
}

Eu vou entrar nesta competição de bits, já que tenho uma resposta que também usa bit-fiddling para decodificar hexadecimais. Observe que o uso de matrizes de caracteres pode ser ainda mais rápido, já que 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.


Outra maneira é usar stackallocpara reduzir a pressão da 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);
}

Para o desempenho eu iria com a solução drphrozens. Uma pequena otimização para o decodificador poderia ser usar uma tabela para qualquer char para se livrar do "<< 4".

Claramente, as duas chamadas de método são dispendiosas. Se algum tipo de verificação é feito nos dados de entrada ou saída (pode ser CRC, checksum ou qualquer outro), o mesmo if (b == 255)...pode ser ignorado e, portanto, o método também é chamado.

Usando offset++e em offsetvez de offsete offset + 1pode dar algum benefício teórico, mas eu 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]));
}

Isto é apenas fora do topo da minha cabeça e não foi testado ou benchmarked.





hex