Обмен байтами целого числа в Java

Sep 14 2020

У меня есть эта крошечная функция, которая ожидает intи возвращает intс замененными байтами:

public class Main {
    
    public static void main(String[] args) {
        int cafeBabe = 0xcafebabe;
        System.out.println(Integer.toHexString(cafeBabe));
        System.out.println(Integer.toHexString(byteSwap(cafeBabe)));
        System.out.println(Integer.toHexString(byteSwap(byteSwap(cafeBabe))));
    }
    
    public static int byteSwap(int a) {
        return ((a & 0xff000000) >>> 24) |
               ((a & 0x00ff0000) >>> 8)  |
               ((a & 0x0000ff00) << 8)   |
               ((a & 0x000000ff) << 24);
    }
}

В основном мы переключаемся между порядком байтов. Итак, есть ли лучший / более эффективный способ выполнения задачи?

Ответы

15 AJNeufeld Sep 14 2020 at 10:47

Историческая справка

Самый первый комментарий к вопросу ОП - задолго до того, как были опубликованы какие-либо ответы - был вопросом, заданным мной, и он очень многозначительно спросил:

Без использования Integer.reverseBytes (cafeBabe) ?

Вопрос был двояким:

  1. Укажите OP, что существует встроенный метод, и, вероятно, это наиболее оптимальный способ сделать это.
  2. Спросите, не пытаются ли они изобрести велосипед .

Этот комментарий / вопрос был удален. Если OP ответил на это, ответ также был удален, и я его никогда не видел.

Этот ответ не защищает OP изобретать колесо . В Java JVM / JIT реализован наиболее эффективный способ выполнения операции с использованием внутренних функций (таких как @HotSpotIntrinsicCandidate), которые не обязательно доступны конечному пользователю.

Изобретая колесо

public static int byteSwap(int a) {
    return ((a & 0xff000000) >>> 24) |
           ((a & 0x00ff0000) >>> 8)  |
           ((a & 0x0000ff00) << 8)   |
           ((a & 0x000000ff) << 24);
}

Эта функция использует 6 различных констант, 4 операции И, 4 сдвига и 3 операции ИЛИ.

Нет смысла использовать операцию AND внутри ((a & 0xff000000) >>> 24)или in, ((a & 0x000000ff) << 24)поскольку 24 бита, которые маскируются, в любом случае немедленно сдвигаются. Это удаляет 2 константы и 2 операции И, что сокращает код до:

public static int byteSwap(int a) {
    return (a >>> 24)               |
           ((a & 0x00ff0000) >>> 8) |
           ((a & 0x0000ff00) << 8)  |
           (a << 24);
}

Изменяя порядок выполнения второй операции, мы можем удалить другую константу 0x00ff0000, и вместо нее повторно использовать 0xff00константу. Это исключает загрузку третьей константы битовой маски, сохраняя дополнительные инструкции байтового кода JVM:

public static int byteSwap(int a) {
    return (a >>> 24)           |
           ((a >>> 8) & 0xff00) |
           ((a & 0xff00) << 8)  |
           (a << 24);
}

Эта переработанная версия очень похожа на встроенный Integer.reverseBytes()код в системной библиотеке:

@HotSpotIntrinsicCandidate
public static int reverseBytes(int i) {
    return (i << 24)            |
           ((i & 0xff00) << 8)  |
           ((i >>> 8) & 0xff00) |
           (i >>> 24);
}

Я не знаю, увеличивает ли разница в упорядочивании какую-либо дополнительную скорость, но (доллары в пончики), @HotSpotIntrinsicCandidateвероятно, делает аннотацию - поэтому просто используйте встроенную функцию.

6 Bobby Sep 14 2020 at 03:25

Мне нравится, просто читать.


Лично я предпочитаю оставлять операторы на новой строке, чтобы было легче увидеть, как строки принадлежат друг другу:

return ((a & 0xff000000) >>> 24)
     | ((a & 0x00ff0000) >>> 8)
     | ((a & 0x0000ff00) << 8)
     | ((a & 0x000000ff) << 24);

Чтобы следовать вашему соглашению о форматировании.


Вы можете поместить первый и последний байт друг под другом, чтобы узор был лучше виден:

return ((a & 0xff000000) >>> 24)
     | ((a & 0x000000ff) << 24)
     | ((a & 0x00ff0000) >>> 8)
     | ((a & 0x0000ff00) << 8);

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


Кроме того, можно преобразовать intв byteмассив (например , с помощью ByteBuffer), и обратный этот массив до преобразования его обратно. Но что касается эффективности, я думаю, что это должен быть тот, у которого меньше всего операций, поскольку в основном любая операция с byteмассивом должна делать то же самое и даже больше.


Как уже отмечалось, это уже Integer.reverseBytes(int)доступно с версии 1.5.