Обмен байтами целого числа в Java
У меня есть эта крошечная функция, которая ожидает 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);
}
}
В основном мы переключаемся между порядком байтов. Итак, есть ли лучший / более эффективный способ выполнения задачи?
Ответы
Историческая справка
Самый первый комментарий к вопросу ОП - задолго до того, как были опубликованы какие-либо ответы - был вопросом, заданным мной, и он очень многозначительно спросил:
Без использования Integer.reverseBytes (cafeBabe) ?
Вопрос был двояким:
- Укажите OP, что существует встроенный метод, и, вероятно, это наиболее оптимальный способ сделать это.
- Спросите, не пытаются ли они изобрести велосипед .
Этот комментарий / вопрос был удален. Если 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вероятно, делает аннотацию - поэтому просто используйте встроенную функцию.
Мне нравится, просто читать.
Лично я предпочитаю оставлять операторы на новой строке, чтобы было легче увидеть, как строки принадлежат друг другу:
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.