Может ли кто-нибудь объяснить преобразование массива байтов в шестнадцатеричную строку?

Недавно я начал изучать хеширование MD5 (в Java), и, хотя я нашел алгоритмы и методы, которые помогут мне в этом, мне остается только гадать, как это на самом деле работает.

Во-первых, я нашел следующее из этого URL:

private static String convertToHex(byte[] data) {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < data.length; i++) {
        int halfbyte = (data[i] >>> 4) & 0x0F;
        int two_halfs = 0;
        do {
            if ((0 <= halfbyte) && (halfbyte <= 9))
                buf.append((char) ('0' + halfbyte));
            else
                buf.append((char) ('a' + (halfbyte - 10)));
                halfbyte = data[i] & 0x0F;
            } while(two_halfs++ < 1);
        }
    return buf.toString();
}

Я не нашел необходимости использовать битовый сдвиг в Java, поэтому я немного заржавел в этом. Кто-нибудь достаточно любезен, чтобы проиллюстрировать (простыми словами), как именно приведенный выше код выполняет преобразование? ">>>"?

Я также нашел другие решения в StackOverflow, такие как здесь и здесь, который вместо этого использует BigInteger:

try {
   String s = "TEST STRING";
   MessageDigest md5 = MessageDigest.getInstance("MD5");
   md5.update(s.getBytes(),0,s.length());
   String signature = new BigInteger(1,md5.digest()).toString(16);
   System.out.println("Signature: "+signature);

} catch (final NoSuchAlgorithmException e) {
   e.printStackTrace();
}

Почему это тоже работает и какой способ более эффективен?

Спасибо за ваше время.


person aberrant80    schedule 25.06.2009    source источник


Ответы (4)


private static String convertToHex(byte[] data) {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i < data.length; i++) {

До этого момента... просто базовая настройка и запуск цикла для прохождения всех байтов в массиве

        int halfbyte = (data[i] >>> 4) & 0x0F;

байты при преобразовании в шестнадцатеричный формат представляют собой две шестнадцатеричные цифры или 8 двоичных цифр в зависимости от того, в какой базе вы их смотрите. Вышеприведенный оператор сдвигает старшие 4 бита вниз (>>> — беззнаковый сдвиг вправо) и выполняет логическое И с 0000 1111, так что результатом является целое число, равное старшим 4 битам байта (первая шестнадцатеричная цифра).

Скажем, 23 было входом, это 0001 0111 в двоичном формате. Сдвиг делает и логически И скрывает это до 0000 0001.

        int two_halfs = 0;
        do {

Это просто настраивает цикл do/while на выполнение дважды.

            if ((0 <= halfbyte) && (halfbyte <= 9))
                buf.append((char) ('0' + halfbyte));
            else
                buf.append((char) ('a' + (halfbyte - 10)));

Здесь мы отображаем фактическую шестнадцатеричную цифру, в основном просто используя ноль или символ в качестве отправной точки и переходя к правильному символу. Первый оператор if охватывает все цифры от 0 до 9, а второй охватывает все цифры от 10 до 15 (a-f в шестнадцатеричном формате).

Опять же, используя наш пример, 0000 0001 в десятичном виде равен 1. Мы попадаем в верхний блок if и добавляем 1 к символу «0», чтобы получить символ «1», добавляем его к строке и идем дальше.

                halfbyte = data[i] & 0x0F;

Теперь мы устанавливаем целое число так, чтобы оно просто равнялось младшим битам байта, и повторяем.

Опять же, если наш ввод был 23 ... 0001 0111 после логического И становится просто 0000 0111, что равно 7 в десятичном виде. Повторите ту же логику, что и выше, и отобразится символ «7».

            } while(two_halfs++ < 1);

Теперь мы просто переходим к следующему байту в массиве и повторяем.

        }
    return buf.toString();
}

Чтобы ответить на ваш следующий вопрос, Java API уже имеет базовую утилиту преобразования, встроенную в BigInteger. См. toString(int radix ) документация.

Не зная реализации, используемой Java API, я не могу сказать наверняка, но готов поспорить, что реализация Java более эффективна, чем первый несколько простой алгоритм, который вы опубликовали.

person tschaible    schedule 25.06.2009
comment
+1 за усилия и за то, что опередили меня. Единственное, что я хотел бы добавить, это ссылку на документацию по побитовым операциям: j2ee.me/docs/books/tutorial/java/nutsandbolts/op3.html - person Welbog; 25.06.2009
comment
Большое спасибо за подробное объяснение! - person aberrant80; 25.06.2009
comment
Когда вы сдвигаете исходные биты на 4 вправо, вы получаете 4 нуля и первые 4 бита из исходного ввода. Зачем в данном случае выполнять &0x0F - разницы не вижу (последние 4 бита всегда остаются одинаковыми)? Однако второе выполнение &0x0F мне ясно. - person sventevit; 26.02.2013

Чтобы ответить на этот бит:

Почему это тоже работает

Это не так. По крайней мере, не так, как версия с циклом. new BigInteger(...).toString(16) не будет отображать ведущие нули, как в предыдущей версии. Обычно для чего-то вроде записи массива байтов (особенно того, который представляет что-то вроде хэша) вам нужен вывод фиксированной длины, поэтому, если вы хотите использовать эту версию, вам придется ее соответствующим образом заполнить.

person Cowan    schedule 26.06.2009
comment
Спасибо, что заметили разницу. - person aberrant80; 29.06.2009

Подробное объяснение побитового сдвига см. в ответах на следующий вопрос SO Что такое побитовый сдвиг ( битовый сдвиг) и как они работают?

Кажется, он пытается преобразовать один единственный байт в число меньше 16, тем самым он может легко определить, какой символ представляет этот байт с помощью кода.

  if ((0 <= halfbyte) && (halfbyte <= 9))
                buf.append((char) ('0' + halfbyte));
            else
                buf.append((char) ('a' + (halfbyte - 10)));

Это упрощенный ответ, но я все равно не такой умный =D

person Nuno Furtado    schedule 25.06.2009

Эти вещи вам не нужно писать самостоятельно, потому что они уже написаны в apache-commons-codec:

import org.apache.commons.codec.binary.Hex;
...
Hex.encodeHexString(byte[] array)

В классе Hex есть еще много полезных методов.

person kavai77    schedule 28.04.2014