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

ヒストリカルノート

OPの質問に対する最初のコメントは、回答が投稿されるずっと前に、私自身が尋ねた質問であり、非常に鋭く尋ねられました。

Integer.reverseBytes(cafeBabe)を使用せずに?

質問は2つありました:

  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つのAND演算、4つのシフト、および3つのOR演算を使用します。

とにかくマスクされている24ビットはすぐにシフトアウトされるので、AND演算が入っ((a & 0xff000000) >>> 24)たり入ったりし((a & 0x000000ff) << 24)ても意味がありません。これにより、2つの定数と2つのAND演算が削除され、コードが次のように削減されます。

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

2番目の操作の順序を作り直すことで、別の定数、を削除し0x00ff0000、代わりに0xff00定数を再利用できます。これにより、3番目のビットマスク定数のロードが不要になり、追加の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以降で利用可能なものがすでにあります。