C# – Hex-String zu Byte-Array

C# – Hex-String zu Byte-Array

Dieser Artikel zeigt Code zum Konvertieren eines Hex-Strings in ein Byte-Array, Unit-Tests und einen Geschwindigkeitsvergleich.

Zunächst zeigt dieses Diagramm den Algorithmus zum Konvertieren einer Hex-Zeichenfolge in ein Byte-Array.

Um einen Hex-String in ein Byte-Array umzuwandeln, müssen Sie den Hex-String durchlaufen und jeweils zwei Zeichen in ein Byte umwandeln. Dies liegt daran, dass jedes Hexadezimalzeichen ein halbes Byte darstellt.

Hex-String zu Byte-Array-Code

Der folgende Code konvertiert einen Hex-String in ein Byte-Array. Es verwendet einen Lookup + Bit-Shift-Ansatz.

Es überprüft die Hex-String-Eingabe auf Fehler, behandelt gemischte Groß- und Kleinschreibung und überspringt das beginnende „0x“, falls vorhanden. Ich glaube, es ist sehr wichtig, immer nach Fehlerbedingungen zu suchen und Sonderfälle zu behandeln.

public static class HexUtil
{
	private readonly static Dictionary<char, byte> hexmap = new Dictionary<char, byte>()
	{
		{ 'a', 0xA },{ 'b', 0xB },{ 'c', 0xC },{ 'd', 0xD },
		{ 'e', 0xE },{ 'f', 0xF },{ 'A', 0xA },{ 'B', 0xB },
		{ 'C', 0xC },{ 'D', 0xD },{ 'E', 0xE },{ 'F', 0xF },
		{ '0', 0x0 },{ '1', 0x1 },{ '2', 0x2 },{ '3', 0x3 },
		{ '4', 0x4 },{ '5', 0x5 },{ '6', 0x6 },{ '7', 0x7 },
		{ '8', 0x8 },{ '9', 0x9 }
	};
	public static byte[] ToBytes(this string hex)
	{
		if (string.IsNullOrWhiteSpace(hex))
			throw new ArgumentException("Hex cannot be null/empty/whitespace");

		if (hex.Length % 2 != 0)
			throw new FormatException("Hex must have an even number of characters");

		bool startsWithHexStart = hex.StartsWith("0x", StringComparison.OrdinalIgnoreCase);

		if (startsWithHexStart && hex.Length == 2)
			throw new ArgumentException("There are no characters in the hex string");


		int startIndex = startsWithHexStart ? 2 : 0;

		byte[] bytesArr = new byte[(hex.Length - startIndex) / 2];

		char left;
		char right;

		try 
		{ 
			int x = 0;
			for(int i = startIndex; i < hex.Length; i += 2, x++)
			{
				left = hex[i];
				right = hex[i + 1];
				bytesArr[x] = (byte)((hexmap[left] << 4) | hexmap[right]);
			}
			return bytesArr;
		}
		catch(KeyNotFoundException)
		{
			throw new FormatException("Hex string has non-hex character");
		}
	}
}
Code language: C# (cs)

Hex-String zu Byte-Array-Tests

Hier sind die Komponententests, die überprüfen, ob der Code Fehlerfälle behandelt, verschiedene Eingabeformate verarbeitet und das Hex korrekt in ein Byte-Array konvertiert.

  • unter Verwendung von Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass()]
public class HexUtilTests
{
	[DataRow(null)]
	[DataRow("")]
	[DataRow(" ")]
	[DataTestMethod()]
	public void HexToByteArray_WhenNullEmptyOrWhitespace_ThrowsArgumentException(string hex)
	{
		//act & assert
		Assert.ThrowsException<ArgumentException>(() => hex.ToBytes());
	}
	[TestMethod()]
	public void HexToByteArray_WhenOddLength_ThrowsFormatException()
	{
		//arrange
		string hex = "A";

		//act & assert
		Assert.ThrowsException<FormatException>(() =>hex.ToBytes());
	}
	[DataRow("0x")]
	[DataRow("0X")]
	[DataTestMethod()]
	public void HexToByteArray_WhenStartsWithHexStart_AndNoDigitsAfter_ThrowsArgumentException(string hex)
	{
		//act && assert
		Assert.ThrowsException<ArgumentException>(() => hex.ToBytes());
	}
	[TestMethod]
	public void HexToByteArray_WhenHasUpperCaseLetters_ConvertsThemToBytes()
	{
		//arrange
		string hex = "ABCDEF";
		byte[] expected = new byte[]
		{
			0xAB,
			0xCD,
			0xEF
		};

		//act
		var actual = hex.ToBytes();

		//arrange
		CollectionAssert.AreEqual(expected, actual);

	}
	[DataRow("AM")]
	[DataRow("A!")]
	[TestMethod()]
	public void HexToByteArray_WhenHasInvalidHexCharacter_ThrowsFormatException(string hex)
	{
		//act && assert
		Assert.ThrowsException<FormatException>(() => hex.ToBytes());
	}
	[DataRow("0xab")]
	[DataRow("0Xab")]
	[DataRow("ab")]
	[TestMethod()]
	public void HexToByteArray_WhenHasLowercaseHexCharacters_ReturnsByteArray(string hex)
	{
		//arrange
		byte[] expected = new byte[] { 0xAB };

		//act
		var actual = hex.ToBytes();

		//act && assert
		CollectionAssert.AreEqual(expected, actual);
	}
	[DataRow("0xAB")]
	[DataRow("0XAB")]
	[DataRow("AB")]
	[TestMethod()]
	public void HexToByteArray_WhenHasUppercaseHexCharacters_ReturnsByteArray(string hex)
	{
		//arrange
		byte[] expected = new byte[] { 0xAB };

		//act
		var actual = hex.ToBytes();

		//act && assert
		CollectionAssert.AreEqual(expected, actual);
	}
	[DataRow("0x12")]
	[DataRow("0X12")]
	[DataRow("12")]
	[TestMethod()]
	public void HexToByteArray_WhenHasDigits_ReturnsByteArray(string hex)
	{
		//arrange
		byte[] expected = new byte[] { 0x12 };

		//act
		var actual = hex.ToBytes();

		//act && assert
		CollectionAssert.AreEqual(expected, actual);
	}
}
Code language: C# (cs)

Geschwindigkeitsvergleich – Lookup/Shift vs. Linq

Ich habe diesen Code mit einem einzeiligen Linq-Ansatz verglichen (der keine Fehlerbehandlung hat).

Ich habe eine zufällige Hex-Zeichenfolge mit gemischter Groß- und Kleinschreibung generiert und dann die beiden Konverter 10 Mal ausgeführt. Hier sind die durchschnittlichen Zeiten in Millisekunden für jede Eingabegröße.

32 Zeichen 320 Zeichen 3.200 Zeichen 32.000 Zeichen 320.000 Zeichen 3.200.000 Zeichen
Nachschlagen/Verschieben 0,0007 ms 0,013 ms 0,056 ms 0,428 ms 5 ms 41 ms
Linq 0,0043 ms 0,049 ms 0,121 ms 1,173 ms 13,4 ms 103 ms

Der Lookup/Shift-Ansatz ist in der Regel 2,5-mal schneller als der Linq-Ansatz, selbst bei kleineren Eingabegrößen.

Generieren zufälliger Hex-Strings

Der folgende Code generiert eine zufällige Hex-Zeichenfolge mit gemischter Groß-/Kleinschreibung. Sie geben die Anzahl der Iterationen an, und das Endergebnis ist eine Hex-Zeichenfolge mit 32 Zeichen für jede Iteration. Mit anderen Worten, wenn Sie 100.000 angeben, wird ein Hex-String mit 3.200.000 Zeichen generiert.

var randomHex = string.Join("", Enumerable.Range(0, 100_000).Select(t =>
{
	var guidHex = Guid.NewGuid().ToString().Replace("-", "");

	return t % 2 == 0 ? guidHex : guidHex.ToUpper();
}));
Code language: C# (cs)