чисел - преобразовать строку в массив байт c#




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

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


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

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

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

отказ

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

Результаты

  • Поиск по байтам unsafe (через CodesInChaos) (добавлен в тестовое репо с помощью airbreather )
    • Текст: 4 727,85 (105,2X)
    • Приговор: 0,28 (99,7X)
  • Поиск по байтам (через CodesInChaos)
    • Текст: 10 853,96 (45,8X быстрее)
    • Предложение: 0,65 (на 42,7 раза быстрее)
  • Манипуляция байтов 2 (через CodesInChaos)
    • Текст: 12 967,69 (38,4 раза быстрее)
    • Предложение: 0,73 (37,9 раза быстрее)
  • Манипуляция байтами (через Waleed Eissa)
    • Текст: 16 856,64 (на 29,5 раза быстрее)
    • Предложение: 0.70 (на 39.5X быстрее)
  • Поиск / смена (через Натана Моинвазири)
    • Текст: 23,201,23 (21,4 раза быстрее)
    • Предложение: 1,24 (на 22,3 раза быстрее)
  • Поиск по грызуну (через Брайана Ламберта)
    • Текст: 23 879,41 (на 20,8 раза быстрее)
    • Приговор: 1,15 (на 23,9 раза быстрее)
  • BitConverter (через Tomalak)
    • Текст: 113 269,34 (4,4 раза быстрее)
    • Предложение: 9,98 (2,8 раза быстрее)
  • {SoapHexBinary}.ToString (через Mykroft)
    • Текст: 178,601.39 (2.8X быстрее)
    • Предложение: 10,68 (2,6 раза быстрее)
  • {byte}.ToString("X2") (используя foreach ) (полученный от ответа Дина Дина)
    • Текст: 308 805,38 (на 2,4 раза быстрее)
    • Предложение: 16,89 (2,4 раза быстрее)
  • {byte}.ToString("X2") (используя {IEnumerable}.Aggregate , требуется System.Linq) (через Mark)
    • Текст: 352 828,20 (на 2,1 раза быстрее)
    • Предложение: 16,87 (2,4 раза быстрее)
  • Array.ConvertAll (используя string.Join ) (через Will Dean)
    • Текст: 675 451,57 (на 1,1 раза быстрее)
    • Приговор: 17,95 (2,2 раза быстрее)
  • Array.ConvertAll (используя string.Concat , требуется .NET 4.0) (через Will Dean)
    • Текст: 752 078,70 (на 1,0X быстрее)
    • Предложение: 18,28 (2,2 раза быстрее)
  • {StringBuilder}.AppendFormat (используя foreach ) (через Tomalak)
    • Текст: 672,115.77 (на 1,1 раза быстрее)
    • Предложение: 36,82 (1,1 раза быстрее)
  • {StringBuilder}.AppendFormat (используя {IEnumerable}.Aggregate , требуется System.Linq) (полученное из ответа {IEnumerable}.Aggregate )
    • Текст: 718,380.63 (на 1.0X быстрее)
    • Предложение: 39,71 (1,0X быстрее)

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

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

Тестирование кода

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Вы можете использовать метод 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 (байт [])


Если вы хотите больше гибкости, чем 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")));

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


Или:

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

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

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

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 .


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

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

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

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


Существует класс под названием 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();
}

Это ответ на пересмотренный вариант 4 share (и последующих изменений).

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

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

warning: этот ответ может устареть, если в инфраструктуре реализована перегрузка 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

Повеселись! (Но оптимизируйте с умеренностью.)


Методы расширения (отказ от ответственности: полностью непроверенный код, 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();
    }
}

и т. д. Используйте любое из трех решений Tomalak (с последним - метод расширения в строке).


Два mashups, которые складывают две операции полубайта в одну.

Вероятно, довольно эффективная версия:

public static string ByteArrayToString2(byte[] ba)
{
    char[] c = new char[ba.Length * 2];
    for( int i = 0; i < ba.Length * 2; ++i)
    {
        byte b = (byte)((ba[i>>1] >> 4*((i&1)^1)) & 0xF);
        c[i] = (char)(55 + b + (((b-10)>>31)&-7));
    }
    return new string( c );
}

Декадентская версия linq-with-bit-hacking:

public static string ByteArrayToString(byte[] ba)
{
    return string.Concat( ba.SelectMany( b => new int[] { b >> 4, b & 0xF }).Select( b => (char)(55 + b + (((b-10)>>31)&-7))) );
}

И наоборот:

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

Дополнение для ответа на @CodesInChaos (обратный метод)

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

Объяснение:

& 0x0f должен поддерживать также строчные буквы

hi = hi + 10 + ((hi >> 31) & 7); такой же как:

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

Для '0' .. '9' это то же самое, hi = ch - 65 + 10 + 7;что и есть hi = ch - 48(это из-за 0xffffffff & 7).

Для «A» .. «F» это hi = ch - 65 + 10;(это из-за 0x00000000 & 7).

Для 'a' .. 'f' мы имеем большие числа, поэтому мы должны вычесть 32 из версии по умолчанию, сделав несколько бит 0, используя & 0x0f.

65 - код для 'A'

48 - код для '0'

7 - количество букв между '9'и 'A'в таблице ASCII ( ...456789:;<=>[email protected]).


Еще одна разновидность разнообразия:

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

И для вставки в строку SQL (если вы не используете параметры команды):

public static String ByteArrayToSQLHexString(byte[] Source)
{
    return = "0x" + BitConverter.ToString(Source).Replace("-", "");
}

Обратная функция для кода 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;
    }

Функция Waleed Eissa с поддержкой нижнего регистра:

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

От разработчиков Microsoft, приятное, простое преобразование:

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

В то время как вышесказанное является чистым, компактные специалисты по производительности будут кричать об этом с помощью счетчиков. Вы можете получить максимальную производительность с улучшенной версией оригинального ответа 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();   
} 

Это самая быстрая из всех описанных здесь процедур. Не просто возьмите мое слово за это ... тест производительности каждой рутины и проверьте свой код CIL для себя.


Эта версия ByteArrayToHexViaByteManipulation может быть быстрее.

Из моих отчетов:

  • ByteArrayToHexViaByteManipulation3: 1,68 средних тика (более 1000 прогонов), 17,5X
  • ByteArrayToHexViaByteManipulation2: 1,73 средних тика (более 1000 прогонов), 16,9X
  • ByteArrayToHexViaByteManipulation: 2,90 средних тиков (более 1000 прогонов), 10,1X
  • ByteArrayToHexViaLookupAndShift: 3,22 средних тика (более 1000 прогонов), 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);
    }
    

И я думаю, что это оптимизация:

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

Вот мой выстрел в него. Я создал пару классов расширения для расширения строки и байта. При большом пробном файле производительность сопоставима с Byte Manipulation 2.

Код ниже для ToHexString - это оптимизированная реализация алгоритма поиска и сдвига. Он почти идентичен файлу Behrooz, но оказывается, что он используется foreachдля итерации, а счетчик быстрее, чем явно индексирование for.

Он занимает второе место позади Byte Manipulation 2 на моей машине и очень читаемый код. Интересны также следующие результаты испытаний:

ToHexStringCharArrayWithCharArrayLookup: 41,589.69 средних тиков (более 1000 прогонов), 1.5X ToHexStringCharArrayWithStringLookup: 50 764,06 средних тиков (более 1000 прогонов), 1.2X ToHexStringStringBuilderWithCharArrayLookup: 62,812.87 средних тиков (более 1000 прогонов), 1.0X

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

  1. Штрафы за индексацию в строку для выполнения поиска по сравнению с массивом char значительны в большом пробном файле.
  2. Шансы на использование StringBuilder известной емкости по сравнению с массивом char известного размера для создания строки еще более значительны.

Вот код:

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

Ниже приведены результаты тестов, которые я получил, когда я поместил свой код в проект тестирования @ patridge на моей машине. Я также добавил тест для преобразования в массив байтов из шестнадцатеричного. Тесты, которые выполняли мой код, - это ByteArrayToHexViaOptimizedLookupAndShift и HexToByteArrayViaByteManipulation. HexToByteArrayViaConvertToByte был взят из XXXX. HexToByteArrayViaSoapHexBinary - это ответ от @ Mykroft.

Процессор Intel Pentium III Xeon

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

Преобразование массива байтов в шестнадцатеричное строковое представление

ByteArrayToHexViaByteManipulation2: 39 366,64 средних тика (более 1000 прогонов), 22,4X

ByteArrayToHexViaOptimizedLookupAndShift: 41 588,64 средних тика (более 1000 прогонов), 21,2X

ByteArrayToHexViaLookup: 55 509,56 средних тиков (более 1000 прогонов), 15,9X

ByteArrayToHexViaByteManipulation: 65,349.12 средний тик (более 1000 прогонов), 13,5X

ByteArrayToHexViaLookupAndShift: 86 926,87 средних тиков (более 1000 прогонов), 10,2X

ByteArrayToHexStringViaBitConverter: 139,353.73 средний тик (более 1000 прогонов), 6.3X

ByteArrayToHexViaSoapHexBinary: 314 598,77 средних тиков (более 1000 прогонов), 2,8X

ByteArrayToHexStringViaStringBuilderForEachByteToString: 344,264.63 средний тик (более 1000 прогонов), 2,6X

ByteArrayToHexStringViaStringBuilderAggregateByteToString: 382 623,44 средних тика (более 1000 прогонов), 2,3X

ByteArrayToHexStringViaStringBuilderForEachAppendFormat: 818,111.95 средних тиков (более 1000 прогонов), 1.1X

ByteArrayToHexStringViaStringConcatArrayConvertAll: 839,244.84 средних тиков (более 1000 прогонов), 1.1X

ByteArrayToHexStringViaStringBuilderAggregateAppendFormat: 867 303,98 средних тиков (более 1000 прогонов), 1,0X

ByteArrayToHexStringViaStringJoinArrayConvertAll: 882,710.28 средних тиков (более 1000 прогонов), 1.0X


Другой способ - stackallocуменьшить давление ГХ-памяти:

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

Зачем это сложно? Это просто в Visual Studio 2008:

C #:

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

VB:

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

Это отличная статья. Мне нравится решение Валида. Я не запускал его через тест patridge, но, похоже, он довольно быстро. Мне также нужен обратный процесс, преобразование шестнадцатеричной строки в массив байтов, поэтому я написал ее как обращение к решению Waleed. Не уверен, что это быстрее, чем оригинальное решение Tomalak. Опять же, я не запускал обратный процесс через тест patridge.

private byte[] HexStringToByteArray(string hexString)
{
    int hexStringLength = hexString.Length;
    byte[] b = new byte[hexStringLength / 2];
    for (int i = 0; i < hexStringLength; i += 2)
    {
        int topChar = (hexString[i] > 0x40 ? hexString[i] - 0x37 : hexString[i] - 0x30) << 4;
        int bottomChar = hexString[i + 1] > 0x40 ? hexString[i + 1] - 0x37 : hexString[i + 1] - 0x30;
        b[i / 2] = Convert.ToByte(topChar + bottomChar);
    }
    return b;
}

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

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

Преобразован из Java-кода.





hex