Как преобразовать байтовый массив в шестнадцатеричную строку и наоборот? - PullRequest
1261 голосов
/ 22 ноября 2008

Как преобразовать байтовый массив в шестнадцатеричную строку и наоборот?

Ответы [ 40 ]

1226 голосов
/ 22 ноября 2008

Или:

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

или

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

Есть еще больше вариантов, например здесь .

Обратное преобразование будет выглядеть так:

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

Использование Substring - лучший вариант в сочетании с Convert.ToByte. См. этот ответ для получения дополнительной информации. Если вам нужна лучшая производительность, вы должны избегать Convert.ToByte, прежде чем сможете сбросить SubString.

445 голосов
/ 09 марта 2009

Анализ производительности

Примечание: новый лидер с 2015-08-20.

Я выполнил каждый из различных методов преобразования через некоторое грубое Stopwatch тестирование производительности, прогон со случайным предложением (n = 61, 1000 итераций) и прогон с текстом Project Gutenburg (n = 1 238 957, 150 итераций) , Вот результаты, примерно от самого быстрого до самого медленного. Все измерения даны в тиках ( 10000 тиков = 1 мс ), и все относительные ноты сравниваются с [самой медленной] реализацией StringBuilder. Используемый код см. Ниже или инфраструктура тестирования repo , где я сейчас поддерживаю код для запуска этого.

Ответственность

ВНИМАНИЕ: не полагайтесь на эти характеристики для чего-то конкретного; это просто пример пробных данных. Если вам действительно нужна первоклассная производительность, протестируйте эти методы в среде, представляющей ваши производственные потребности, с данными, указывающими, что вы будете использовать.

Результаты

Таблицы поиска взяли на себя инициативу по манипулированию байтами. По сути, существует некоторая форма предварительного вычисления того, каким будет любой заданный кусочек или байт в шестнадцатеричном виде. Затем, когда вы просматриваете данные, вы просто просматриваете следующую часть, чтобы увидеть, какой это будет шестнадцатеричная строка. Это значение затем добавляется к полученному выводу строки некоторым способом. В течение долгого времени манипулирование байтами, потенциально трудное для чтения некоторыми разработчиками, было наиболее эффективным подходом.

Ваша лучшая ставка по-прежнему будет найти некоторые репрезентативные данные и опробовать их в производственной среде. Если у вас другие ограничения памяти, вы можете предпочесть метод с меньшим количеством выделений, чем метод, который был бы быстрее, но потреблял бы больше памяти.

Тестовый код

Не стесняйтесь играть с кодом тестирования, который я использовал. Версия включена сюда, но вы можете клонировать repo и добавлять свои собственные методы. Пожалуйста, отправьте запрос на удаление, если вы найдете что-нибудь интересное или хотите помочь улучшить используемую им инфраструктуру тестирования.

  1. Добавить новый статический метод (Func<byte[], string>) в /Tests/ConvertByteArrayToHexString/Test.cs.
  2. Добавьте имя этого метода к возвращаемому значению TestCandidates в том же классе.
  3. Убедитесь, что вы используете нужную вам версию ввода, предложение или текст, переключая комментарии в GenerateTestInput в том же классе.
  4. Нажмите F5 и дождитесь вывода (в папке / bin также создается дамп HTML).
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();
}

Обновление (2010-01-13)

Добавлен ответ Валида на анализ. Довольно быстро.

Обновление (2011-10-05)

Добавлен string.Concat Array.ConvertAll вариант для полноты (требуется .NET 4.0). Наравне с string.Join версией.

Обновление (2012-02-05)

Тестовое репо включает в себя больше вариантов, таких как StringBuilder.Append(b.ToString("X2")). Никто не расстроил результаты какие-либо. Например, foreach быстрее, чем {IEnumerable}.Aggregate, но BitConverter все еще выигрывает.

Обновление (2012-04-03)

Добавлен ответ Майкрофта SoapHexBinary на анализ, который занял третье место.

Обновление (2013-01-15)

Добавлен ответ CodesInChaos на манипулирование байтами, который занял первое место (с большим полем для больших блоков текста).

Обновление (2013-05-23)

Добавлен ответ поиска Натана Моинвазири и вариант из блога Брайана Ламберта. Оба довольно быстрые, но не идут впереди на тестовой машине, которую я использовал (AMD Phenom 9750).

Обновление (2014-07-31)

Добавлен новый байтовый ответ @ CodesInChaos. Похоже, что он взял на себя инициативу как по тестам предложений, так и по полнотекстовым тестам.

Обновление (2015-08-20)

Добавлено оптимизация airbreather и unsafe вариант к этому репо ответа . Если вы хотите играть в небезопасную игру, вы можете получить огромный выигрыш в производительности по сравнению с любым из предыдущих лучших победителей как по коротким строкам, так и по крупным текстам.

227 голосов
/ 01 апреля 2010

Есть класс с именем SoapHexBinary , который делает именно то, что вы хотите.

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();
}
135 голосов
/ 15 января 2013

При написании криптокода обычно избегают зависимых от данных ветвей и поиска таблиц, чтобы гарантировать, что время выполнения не зависит от данных, поскольку зависящее от данных время может привести к атакам по побочным каналам.

Это также довольно быстро.

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


Оставь все надежды, входящие сюда

Объяснение странной мелочи:

  1. bytes[i] >> 4 извлекает верхний кусочек байта
    bytes[i] & 0xF извлекает младший кусочек байта
  2. b - 10
    < 0 для значений b < 10, которые станут десятичной цифрой
    >= 0 для значений b > 10, которые станут буквами от A до F.
  3. Использование i >> 31 для 32-разрядного целого числа со знаком выделяет знак благодаря расширению знака. Это будет -1 для i < 0 и 0 для i >= 0.
  4. Сочетание 2) и 3) показывает, что (b-10)>>31 будет 0 для букв и -1 для цифр.
  5. Рассматривая регистр букв, последнее слагаемое становится 0, а b находится в диапазоне от 10 до 15. Мы хотим сопоставить его с A (65) с F (70), что подразумевает добавление 55 ('A'-10).
  6. Рассматривая регистр цифр, мы хотим адаптировать последнее слагаемое, чтобы оно отображало b из диапазона от 0 до 9 в диапазон от 0 (48) до 9 (57). Это означает, что оно должно стать -7 ('0' - 55).
    Теперь мы можем просто умножить на 7. Но так как -1 представлен всеми битами, равными 1, мы можем вместо этого использовать & -7, поскольку (0 & -7) == 0 и (-1 & -7) == -7.

Некоторые дополнительные соображения:

  • Я не использовал вторую переменную цикла для индексации в c, поскольку измерения показывают, что вычисление ее из i обходится дешевле.
  • Использование точно i < bytes.Length в качестве верхней границы цикла позволяет JITter исключить проверки границ для bytes[i], поэтому я выбрал этот вариант.
  • Делая b int, допускает ненужные преобразования из и в байты.
89 голосов
/ 22 ноября 2008

Если вы хотите больше гибкости, чем BitConverter, но не хотите этих неуклюжих явных циклов в стиле 1990-х, то вы можете сделать:

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

Или, если вы используете .NET 4.0:

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

(Последнее из комментария к исходному сообщению.)

62 голосов
/ 21 июня 2014

Еще один подход на основе таблицы поиска. В этом случае используется только одна таблица поиска для каждого байта вместо таблицы поиска для каждого куска.

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

Я также протестировал варианты этого, используя ushort, struct{char X1, X2}, struct{byte X1, X2} в таблице поиска.

В зависимости от цели компиляции (x86, X64) они либо имели примерно одинаковую производительность, либо немного медленнее, чем этот вариант.


А для еще более высокой производительности его 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);
}

Или, если вы считаете приемлемым написать в строку напрямую:

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;
}
62 голосов
/ 22 ноября 2008

Вы можете использовать метод BitConverter.ToString:

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

Выход:

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

Дополнительная информация: Метод BitConverter.ToString (байт [])

53 голосов
/ 11 марта 2009

Я только что столкнулся с той же проблемой сегодня, и я наткнулся на этот код:

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

Источник: сообщение на форуме byte [] Массив в шестнадцатеричную строку (см. Сообщение PZahra). Я немного изменил код, чтобы удалить префикс 0x.

Я провел некоторое тестирование производительности в коде, и это было почти в восемь раз быстрее, чем при использовании BitConverter.ToString () (самый быстрый согласно сообщению Патриджа).

16 голосов
/ 10 октября 2014

Это ответ на редакцию 4 из Очень популярный ответ Томалака (и последующие правки).

Я приведу случай, когда это редактирование неверно, и объясню, почему его можно отменить. Попутно вы можете кое-что узнать о некоторых внутренних элементах и ​​увидеть еще один пример того, что такое преждевременная оптимизация и как она может вас укусить.

tl; dr: Просто используйте Convert.ToByte и String.Substring, если вы спешите («Оригинальный код» ниже), это лучшая комбинация, если вы не хотите повторно реализовать Convert.ToByte. Используйте что-то более продвинутое (см. Другие ответы), которое не использует Convert.ToByte, если вам нужна производительность . не используйте что-либо еще, кроме String.Substring в сочетании с Convert.ToByte, если кто-то не может сказать что-то интересное в комментариях к этому ответу.

предупреждение: Этот ответ может устареть , если в платформе реализована перегрузка a Convert.ToByte(char[], Int32). Это вряд ли произойдет в ближайшее время.

Как правило, я не очень люблю говорить «не оптимизировать преждевременно», потому что никто не знает, когда «преждевременно». Единственное, что вы должны учитывать при принятии решения об оптимизации или нет: «Есть ли у меня время и ресурсы для правильного изучения подходов к оптимизации?». Если вы этого не сделаете, тогда слишком рано, подождите, пока ваш проект станет более зрелым или пока вам не понадобится производительность (если есть реальная потребность, тогда вы сделаете время). А пока сделайте самое простое, что могло бы сработать.

Оригинальный код:

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

Редакция 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;
    }

Ревизия избегает String.Substring и использует вместо нее StringReader. Данная причина:

Редактировать: вы можете улучшить производительность для длинных строк, используя один передать парсер, вот так:

Ну, если посмотреть на ссылочный код для String.Substring, он уже явно "однопроходный"; а почему не должно быть? Он работает на уровне байтов, а не на суррогатных парах.

Тем не менее, он выделяет новую строку, но тогда вам нужно выделить одну для передачи Convert.ToByte в любом случае. Кроме того, решение, представленное в ревизии, выделяет еще один объект на каждой итерации (массив из двух символов); Вы можете безопасно поместить это распределение за пределы цикла и повторно использовать массив, чтобы избежать этого.

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

Каждый шестнадцатеричный numeral представляет один октет с использованием двух цифр (символов).

Но тогда зачем звонить StringReader.Read дважды? Просто вызовите его вторую перегрузку и попросите его прочитать сразу два символа в массиве из двух символов; и уменьшите количество звонков на два.

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

То, что у вас осталось, - это программа чтения строк, единственным добавленным «значением» которой является параллельный индекс (внутренний _pos), который вы могли бы объявить самостоятельно (например, j), избыточная переменная длины (внутренняя) _length) и избыточная ссылка на строку ввода (внутренняя _s). Другими словами, это бесполезно.

Если вам интересно, как Read «читает», просто посмотрите на код , все, что он делает, это вызывает String.CopyTo во входной строке. Все остальное - просто накладные расходы на поддержание ценностей, которые нам не нужны.

Итак, удалите устройство чтения строк и вызовите CopyTo самостоятельно; это проще, понятнее и эффективнее.

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

Вам действительно нужен индекс j, который увеличивается с шагом в две параллели к i? Конечно, нет, просто умножьте i на два (что компилятор должен уметь оптимизировать до сложения).

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

Как выглядит решение сейчас? Точно так же, как это было в начале, только вместо того, чтобы использовать String.Substring для выделения строки и копирования в нее данных, вы используете промежуточный массив, в который вы копируете шестнадцатеричные цифры, затем выделяете строку самостоятельно и копируете строку. данные снова из массива и в строку (при передаче в конструкторе строк). Вторая копия может быть оптимизирована, если строка уже находится во внутреннем пуле, но тогда String.Substring также сможет избежать этого в этих случаях.

На самом деле, если вы посмотрите на String.Substring снова, вы увидите, что он использует некоторые внутренние знания низкого уровня того, как строятся строки, чтобы распределить строку быстрее, чем вы могли бы это обычно делать, и он вставляет тот же код, который использовался CopyTo прямо там, чтобы избежать накладных расходов на вызов.

String.Substring

  • Наихудший случай: одно быстрое размещение, одна быстрая копия.
  • В лучшем случае: без выделения, без копии.

Ручной метод

  • Наихудший случай: два обычных размещения, одно обычное копирование, одно быстрое копирование.
  • В лучшем случае: одно нормальное распределение, одна нормальная копия.

Вывод? Если вы хотите использовать Convert.ToByte(String, Int32) (потому что вы не хотите самостоятельно реализовывать эту функцию), похоже, нет способа победить String.Substring; все, что вы делаете, это бегаете кругами, заново изобретая колесо (только с неоптимальными материалами).

Обратите внимание, что использование Convert.ToByte и String.Substring является вполне допустимым выбором, если вам не нужна высокая производительность. Помните: выбирайте альтернативу, только если у вас есть время и ресурсы, чтобы выяснить, как она работает правильно.

Если бы существовал Convert.ToByte(char[], Int32), конечно, все было бы иначе (можно было бы сделать то, что я описал выше, и полностью избежать String).

Я подозреваю, что люди, которые сообщают о лучшей производительности, "избегая String.Substring", также избегают Convert.ToByte(String, Int32), что вы действительно должны делать, если вам все равно нужна производительность. Посмотрите на бесчисленные другие ответы, чтобы открыть для себя все различные подходы к этому.

Отказ от ответственности: я не декомпилировал последнюю версию фреймворка, чтобы убедиться, что справочный источник обновлен, я предполагаю, что это так.

Теперь все это звучит хорошо и логично, надеюсь, даже очевидно, если вам удалось продвинуться так далеко. Но так ли это?

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

Да!

Реквизит в Partridge для каркаса скамейки, его легко взломать. В качестве входных данных используется следующий хэш SHA-1, повторенный 5000 раз для создания строки длиной 100 000 байт.

209113288F93A9AB8E474EA78D899AFDBB874355

Веселись! (Но оптимизировать с помощью модерации.)

15 голосов
/ 08 июня 2011

Эту проблему также можно решить с помощью справочной таблицы. Это потребует небольшого количества статической памяти как для кодера, так и для декодера. Однако этот метод будет быстрым:

  • Таблица кодировщика 512 байт или 1024 байт (дважды размер, если верхний и нижний регистр необходимо)
  • Таблица декодера 256 байтов или 64 КиБ (либо поиск одного символа) или поиск двойного символа)

Мое решение использует 1024 байта для таблицы кодирования и 256 байтов для декодирования.

Декодирование

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

Кодирование

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

Сравнение

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

* это решение

Примечание

Во время декодирования могут возникнуть IOException и IndexOutOfRangeException (если символ имеет слишком высокое значение> 256). Должны быть реализованы методы для де / кодирования потоков или массивов, это просто подтверждение концепции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...