การสลับไบต์ของจำนวนเต็มใน Java

Sep 14 2020

ฉันมีฟังก์ชั่นเล็ก ๆ นี้ที่คาดว่า an 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);
    }
}

โดยทั่วไปเรากำลังสลับไปมาระหว่าง endianess ตอนนี้มีวิธีที่ดีกว่า / มีประสิทธิภาพมากขึ้นในการทำงานให้สำเร็จหรือไม่?

คำตอบ

15 AJNeufeld Sep 14 2020 at 10:47

หมายเหตุทางประวัติศาสตร์

ความคิดเห็นแรกในคำถามของ OP - นานก่อนที่จะมีการโพสต์คำตอบใด ๆ - เป็นคำถามที่ถามโดยตัวฉันเองและถามอย่างตรงไปตรงมา:

โดยไม่ใช้Integer.reverseBytes (cafeBabe) ?

คำถามมีสองเท่า:

  1. ชี้ให้ OP เห็นว่ามีวิธีการในตัวอยู่และน่าจะเป็นวิธีที่ดีที่สุดในการทำเช่นนี้
  2. ถามว่าพวกเขากำลังพยายามสร้างล้อใหม่หรือไม่

ความคิดเห็น / คำถามนั้นถูกลบออก หาก 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)หรือใน((a & 0x000000ff) << 24)เนื่องจาก 24 บิตที่ถูกมาสก์จะถูกเลื่อนออกไปทันที สิ่งนี้จะลบค่าคงที่ 2 ค่าและการดำเนินการ 2 AND ซึ่งจะลดรหัสเป็น:

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อาร์เรย์ (ตัวอย่างเช่นด้วยความช่วยเหลือของ a ByteBuffer) และย้อนกลับอาร์เรย์นั้นก่อนที่จะแปลงกลับ แต่เท่าที่ประสิทธิผลเป็นไปฉันคิดว่านี่ควรเป็นสิ่งที่มีการดำเนินการน้อยที่สุดเนื่องจากโดยพื้นฐานแล้วการดำเนินการใด ๆ บนbyteอาร์เรย์จะต้องทำสิ่งเดียวกันและอื่น ๆ


ดังที่ได้กล่าวไปแล้วInteger.reverseBytes(int)ว่ามีให้ใช้งานแล้วตั้งแต่ 1.5