Hoe converteert u een bytearray naar een hexadecimale tekenreeks en vice versa?

Hoe kun je een bytearray converteren naar een hexadecimale tekenreeks en omgekeerd?


Antwoord 1, autoriteit 100%

U kunt Convert.ToHexStringbeginnend met .NET 5.
Er is ook een methode voor de omgekeerde bewerking: Convert.FromHexString.


Voor oudere versies van .NET kunt u het volgende gebruiken:

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

of:

public static string ByteArrayToString(byte[] ba)
{
  return BitConverter.ToString(ba).Replace("-","");
}

Er zijn nog meer varianten om het te doen, bijvoorbeeld hier.

De omgekeerde conversie zou als volgt gaan:

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

Het gebruik van SubStringis de beste optie in combinatie met Convert.ToByte. Zie dit antwoordvoor meer informatie. Als u betere prestaties nodig heeft, moet u Convert.ToBytevermijden voordat u SubStringkunt laten vallen.


Antwoord 2, autoriteit 34%

Prestatieanalyse

Opmerking: nieuwe leider vanaf 20-08-2015.

Ik heb elk van de verschillende conversiemethoden door een aantal ruwe Stopwatchprestatietests laten lopen, een run met een willekeurige zin (n=61, 1000 iteraties) en een run met een Project Gutenburg-tekst (n= 1.238.957, 150 iteraties). Hier zijn de resultaten, ruwweg van snelst tot langzaamst. Alle metingen zijn in ticks (10.000 ticks = 1 ms) en alle relatieve noten worden vergeleken met de [langzaamste] StringBuilderimplementatie. Voor de gebruikte code, zie hieronder of de repository voor testframeworkwaar ik nu de code onderhoud om dit uit te voeren.

Disclaimer

WAARSCHUWING: vertrouw niet op deze statistieken voor iets concreets; ze zijn gewoon een voorbeeldreeks van voorbeeldgegevens. Als u echt topprestaties nodig heeft, test deze methoden dan in een omgeving die representatief is voor uw productiebehoeften met gegevens die representatief zijn voor wat u gaat gebruiken.

Resultaten

Lookup-tabellen hebben de leiding genomen over bytemanipulatie. Kortom, er is een vorm van voorberekenen wat een bepaalde nibble of byte in hex zal zijn. Terwijl u door de gegevens bladert, zoekt u eenvoudig het volgende gedeelte op om te zien welke hexadecimale tekenreeks het zou zijn. Die waarde wordt dan op de een of andere manier toegevoegd aan de resulterende stringoutput. Lange tijd was bytemanipulatie, mogelijk moeilijker te lezen door sommige ontwikkelaars, de best presterende aanpak.

U kunt het beste nog steeds enkele representatieve gegevens vinden en deze uitproberen in een productie-achtige omgeving. Als u verschillende geheugenbeperkingen heeft, geeft u misschien de voorkeur aan een methode met minder toewijzingen dan aan een methode die sneller zou zijn maar meer geheugen zou verbruiken.

Testcode

Speel gerust met de testcode die ik heb gebruikt. Een versie is hier inbegrepen, maar voel je vrij om de repote klonen en je eigen methoden toe te voegen. Dien een pull-verzoek in als je iets interessants vindt of als je wilt helpen het testkader dat het gebruikt te verbeteren.

  1. Voeg de nieuwe statische methode (Func<byte[], string>) toe aan /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Voeg de naam van die methode toe aan de TestCandidates-retourwaarde in diezelfde klasse.
  3. Zorg ervoor dat u de gewenste invoerversie gebruikt, zin of tekst, door de opmerkingen in GenerateTestInputin diezelfde klasse te wisselen.
  4. Druk op F5en wacht op de uitvoer (er wordt ook een HTML-dump gegenereerd in de map /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();
}

Update (13-01-2010)

Waleeds antwoord aan analyse toegevoegd. Vrij snel.

Update (2011-10-05)

Toegevoegd string.ConcatArray.ConvertAllvariant voor volledigheid (vereist .NET 4.0). Gelijk aan string.Joinversie.

Update (2012-02-05)

Test repo bevat meer varianten zoals StringBuilder.Append(b.ToString("X2")). Geen enkele verstoren de resultaten. foreachis bijvoorbeeld sneller dan {IEnumerable}.Aggregate, maar BitConverterwint nog steeds.

Update (03-04-2012)

Mykroft’s SoapHexBinary-antwoord toegevoegd aan analyse, dat de derde plaats overnam.

Update (2013-01-15)

Antwoord voor bytemanipulatie van CodesInChaos toegevoegd, dat de eerste plaats overnam (met een grote marge op grote blokken tekst).

Update (2013-05-23)

Het opzoekantwoord van Nathan Moinvaziri en de variant van de blog van Brian Lambert toegevoegd. Beide vrij snel, maar niet de leiding nemend op de testmachine die ik gebruikte (AMD Phenom 9750).

Update (2014-07-31)

Het nieuwe op byte gebaseerde opzoekantwoord van @CodesInChaos toegevoegd. Het lijkt de leiding te hebben genomen bij zowel de zinstests als de full-text tests.

Update (2015-08-20)

Toegevoegd airbreather’soptimalisaties en unsafevariant aan deze repo van het antwoord. Als je in het onveilige spel wilt spelen, kun je een aantal enorme prestatiewinsten behalen ten opzichte van een van de eerdere topwinnaars op zowel korte reeksen als grote teksten.


Antwoord 3, autoriteit 16%

Er is een klasse genaamd SoapHexBinarydat doet precies wat je wilt.

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

Antwoord 4, autoriteit 10%

Bij het schrijven van cryptocode is het gebruikelijk om data-afhankelijke branches en tabel-lookups te vermijden om ervoor te zorgen dat de runtime niet afhankelijk is van de data, aangezien data-afhankelijke timing kan leiden tot side-channel aanvallen.

Het is ook behoorlijk snel.

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


Laat alle hoop varen, gij die hier binnenkomt

Een uitleg van het rare gehannes:

  1. bytes[i] >> 4extraheert de hoge nibble van een byte
    bytes[i] & 0xFextraheert de lage nibble van een byte
  2. b - 10
    is < 0voor waarden b < 10, wat een decimaal cijfer wordt
    is >= 0voor waarden b > 10, wat een letter wordt van Atot F.
  3. Gebruik i >> 31op een 32-bits geheel getal met teken extraheert het teken, dankzij tekenextensie.
    Het wordt -1voor i < 0en 0voor i >= 0.
  4. Door 2) en 3) te combineren, blijkt dat (b-10)>>310zal zijn voor letters en -1voor cijfers.
  5. Als we kijken naar de naamval voor letters, wordt de laatste som 0en bligt tussen 10 en 15. We willen het toewijzen aan A(65) tot F(70), wat inhoudt dat er 55 ('A'-10) moet worden toegevoegd.
  6. Als we kijken naar het geval voor cijfers, willen we de laatste som aanpassen zodat het bvan het bereik 0 tot 9 toewijst aan het bereik 0(48) tot 9(57). Dit betekent dat het -7 moet worden ('0' - 55).
    Nu kunnen we gewoon vermenigvuldigen met 7. Maar aangezien -1 wordt weergegeven doordat alle bits 1 zijn, kunnen we in plaats daarvan & -7sinds (0 & -7) == 0en (-1 & -7) == -7.

Enkele verdere overwegingen:

  • Ik heb geen tweede lusvariabele gebruikt om te indexeren in c, omdat uit metingen blijkt dat het berekenen ervan uit igoedkoper is.
  • Met exact i < bytes.Lengthals bovengrens van de lus stelt de JITter in staat om grenscontroles op bytes[i]te elimineren, dus koos ik die variant.
  • Door been int te maken, zijn onnodige conversies van en naar byte mogelijk.

Antwoord 5, autoriteit 7%

Als u meer flexibiliteit wilt dan BitConverter, maar geen onhandige expliciete loops in de jaren 90-stijl, dan kunt u het volgende doen:

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

Of, als u .NET 4.0 gebruikt:

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

(De laatste uit een reactie op de originele post.)


Antwoord 6

Aanvulling op antwoord door @CodesInChaos (omgekeerde methode)

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

Uitleg:

& 0x0fondersteunt ook kleine letters

hi = hi + 10 + ((hi >> 31) & 7);is hetzelfde als:

hi = ch-65 + 10 + (((ch-65) >> 31) & 7);

Voor ‘0’..’9′ is dit hetzelfde als hi = ch - 65 + 10 + 7;wat hi = ch - 48is (dit komt door 0xffffffff & 7).

Voor ‘A’..’F’ is dit hi = ch - 65 + 10;(dit komt door 0x00000000 & 7).

Voor ‘a’..’f’ moeten we grote getallen hebben, dus we moeten 32 aftrekken van de standaardversie door enkele bits 0te maken met behulp van & 0x0f.

65 is de code voor 'A'

48 is code voor '0'

7 is het aantal letters tussen '9'en 'A'in de ASCII-tabel (...456789:;<=>[email protected]).


Antwoord 7

Dit probleem kan ook worden opgelost met een opzoektabel. Dit vereist een kleine hoeveelheid statisch geheugen voor zowel de encoder als de decoder. Deze methode zal echter snel zijn:

  • Encodertabel 512 bytes of 1024 bytes (tweemaal
    de grootte als zowel hoofdletters als kleine letters:
    is nodig)
  • Decodertabel 256 bytes of
    64 KiB (ofwel een enkele char look-up
    of dubbele char opzoeken)

Mijn oplossing gebruikt 1024 bytes voor de coderingstabel en 256 bytes voor decodering.

Decodering

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

Codering

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

Vergelijking

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

* deze oplossing

Opmerking

Tijdens het decoderen kunnen IOException en IndexOutOfRangeException optreden (als een teken een te hoge waarde heeft > 256). Methoden voor het decoderen/decoderen van streams of arrays moeten worden geïmplementeerd, dit is slechts een proof of concept.


Antwoord 8

Dotnet 5-update

Om van byte[](bytearray) naar hexadecimale stringte converteren, gebruik:

System.Convert.ToHexString

var myBytes = new byte[100];
var myString = System.Convert.ToHexString(myBytes);

Om van hexadecimale stringnaar byte[]te converteren, gebruik:

System.Convert.FromHexString

var myString  = "E10B116E8530A340BCC7B3EAC208487B";
var myBytes = System.Convert.FromHexString(myString);

Antwoord 9

Waarom het complex maken? Dit is eenvoudig in Visual Studio 2008:

C#:

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

VB:

Dim hex As String = BitConverter.ToString(YourByteArray).Replace("-", "")

Antwoord 10

Dit is een geweldige post. Ik hou van de oplossing van Waleed. Ik heb de patridge-test niet doorstaan, maar het lijkt vrij snel te zijn. Ik had ook het omgekeerde proces nodig, het converteren van een hex-string naar een bytearray, dus ik schreef het als een omkering van de oplossing van Waleed. Ik weet niet zeker of het sneller is dan de originele oplossing van Tomalak. Nogmaals, ik heb het omgekeerde proces ook niet door de patridge-test gelopen.

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

Antwoord 11

Om niet verder te gaan met de vele antwoorden hier, maar ik vond een redelijk optimale (~4,5x beter dan geaccepteerde), rechttoe rechtaan implementatie van de hex string-parser. Ten eerste, output van mijn tests (de eerste batch is mijn implementatie):

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

De base64- en ‘BitConverter’d’-lijnen zijn er om te testen op juistheid. Merk op dat ze gelijk zijn.

De implementatie:

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

Ik heb wat dingen geprobeerd met unsafeen de (duidelijk overbodige) teken-naar-nibble if-reeks naar een andere methode verplaatst, maar dit was de snelste die het kreeg.

(Ik geef toe dat dit de helft van de vraag beantwoordt. Ik vond dat de string->byte[]-conversie ondervertegenwoordigd was, terwijl de byte[]->string-hoek goed gedekt lijkt te zijn. Dus dit antwoord.)


Antwoord 12

Veilige versies:

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

Onveilige versiesVoor degenen die de voorkeur geven aan prestaties en niet bang zijn voor onveiligheid. Ongeveer 35% sneller ToHex en 10% sneller FromHex.

public static class HexUnsafeHelper
{
    [System.Diagnostics.Contracts.Pure]
    public static unsafe string ToHex(this byte[] value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        const string alphabet = @"0123456789ABCDEF";
        string result = new string(' ', checked(value.Length * 2));
        fixed (char* alphabetPtr = alphabet)
        fixed (char* resultPtr = result)
        {
            char* ptr = resultPtr;
            unchecked
            {
                for (int i = 0; i < value.Length; i++)
                {
                    *ptr++ = *(alphabetPtr + (value[i] >> 4));
                    *ptr++ = *(alphabetPtr + (value[i] & 0xF));
                }
            }
        }
        return result;
    }
    [System.Diagnostics.Contracts.Pure]
    public static unsafe byte[] FromHex(this string value)
    {
        if (value == null)
            throw new ArgumentNullException("value");
        if (value.Length % 2 != 0)
            throw new ArgumentException("Hexadecimal value length must be even.", "value");
        unchecked
        {
            byte[] result = new byte[value.Length / 2];
            fixed (char* valuePtr = value)
            {
                char* valPtr = valuePtr;
                for (int i = 0; i < result.Length; i++)
                {
                    // 0(48) - 9(57) -> 0 - 9
                    // A(65) - F(70) -> 10 - 15
                    int b = *valPtr++; // High 4 bits.
                    int val = ((b - '0') + ((('9' - b) >> 31) & -7)) << 4;
                    b = *valPtr++; // Low 4 bits.
                    val += (b - '0') + ((('9' - b) >> 31) & -7);
                    result[i] = checked((byte)val);
                }
            }
            return result;
        }
    }
}

BTW
Voor benchmarktests moet het alfabet worden geïnitialiseerd telkens wanneer de aangeroepen convert-functie verkeerd is, moet het alfabet const (voor tekenreeks) of statisch alleen-lezen zijn (voor char []). Dan wordt de alfabetische conversie van byte[] naar string net zo snel als versies voor bytemanipulatie.

En natuurlijk moet de test worden gecompileerd in Release (met optimalisatie) en met de debug-optie “Suppress JIT-optimalisatie” uitgeschakeld (hetzelfde voor “Enable Just My Code” als code debugbaar moet zijn).


Antwoord 13

Van de ontwikkelaars van Microsoft, een mooie, eenvoudige conversie:

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

Hoewel het bovenstaande schoon en compact is, zullen prestatiejunkies erover schreeuwen met behulp van tellers. U kunt topprestaties behalen met een verbeterde versie van Tomalaks oorspronkelijke antwoord:

public static string ByteArrayToString(byte[] ba)   
{   
   StringBuilder hex = new StringBuilder(ba.Length * 2);   
   for(int i=0; i < ba.Length; i++)       // <-- Use for loop is faster than foreach   
       hex.Append(ba[i].ToString("X2"));   // <-- ToString is faster than AppendFormat   
   return hex.ToString();   
} 

Dit is de snelste van alle routines die ik hier tot nu toe heb gezien. Geloof me niet zomaar op mijn woord… test elke routine op prestatie en inspecteer de CIL-code zelf.


Antwoord 14

Inverse functie voor Waleed Eissa-code (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;
    }

Waleed Eissa-functie met ondersteuning voor kleine letters:

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

Antwoord 15

Uitbreidingsmethoden(disclaimer: volledig ongeteste code, 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.. Gebruik een van de Tomalaks drie oplossingen(waarvan de laatste een uitbreidingsmethode op een string is).


Antwoord 16

Snelste methode voor ouderwetse mensen… mis je tips

   static public byte[] HexStrToByteArray(string str)
    {
        byte[] res = new byte[(str.Length % 2 != 0 ? 0 : str.Length / 2)]; //check and allocate memory
        for (int i = 0, j = 0; j < res.Length; i += 2, j++) //convert loop
            res[j] = (byte)((str[i] % 32 + 9) % 25 * 16 + (str[i + 1] % 32 + 9) % 25);
        return res;
    }

Antwoord 17

Voor prestaties zou ik de drphrozens-oplossing gebruiken. Een kleine optimalisatie voor de decoder zou kunnen zijn om een ​​tabel voor beide tekens te gebruiken om de “<< 4” kwijt te raken.

Het is duidelijk dat de twee methodeaanroepen duur zijn. Als er een soort controle wordt uitgevoerd op invoer- of uitvoergegevens (kan CRC, controlesom of wat dan ook zijn), kan de if (b == 255)...worden overgeslagen en daarmee ook de methode-aanroepen helemaal .

Het gebruik van offset++en offsetin plaats van offseten offset + 1kan enig theoretisch voordeel opleveren, maar ik vermoed de compiler gaat hier beter mee om dan ik.

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

Dit is net van de bovenkant van mijn hoofd en is niet getest of benchmarkt.

Other episodes