java大小端_java中大小端问题研究

关于大小端在内存中的存储

最近在学习读取音频格式文件的时候碰到一个问题,因为音频文件中有四个字节的int型是以little-endian形式存储的,需要转换为java中的big-endian模式。int转byte[]相对简单,只需要做位移缩窄就行了,但是byte[]转int的时候涉及到负数的问题,负数在计算机内中是以补码的形式存在的,容易出现一些问题。

数据是以二进制(0,1)的形式存储在计算机中,每一个这样的数据被称为bit,bit只能是0或者1,将8个bit位定义为一个byte,这样一个byte就能表示2^8个数据。java中1个字节就是1byte,short类型占2byte,int类型占4byte,long类型占8byte,float类型占8byte,double类型占16byte。

下面主要讲一下byte和int之间的转换。

java中没有unsinged类型,也就是byte只能是有符号类型,为了表示正负号,一般使用最高位来表示正负。比如byte i=6,转为二进制码为0000 0110,,在内存中存储如下:1

2

3

4

5

6+------------------------------+

| 0 | 0 | 0 | 0 | 0 | 1 | 1| 0 |

+------------------------------+

^

|

符号位

负数的存储相对比较麻烦,是以补码的形式存储的,补码就是对原有的正数所有的位取反码然后加1;

比如byte i=-6,则相当于对6取反码然后加1;1

2

30000 0110 取反码为 1111 1001

1111 1001 加1为 11111010

则可以得到最后的结果为11111010,在内存中存储如下:1

2

3

4

5

6+------------------------------+

| 1 | 1 | 1 | 1 | 1 | 0 | 1| 0 |

+------------------------------+

^

|

符号位

此处可以有一个简单的验证方法,即原码+补码会越位进1。1

2

3

40000 0110

+ 1111 1010

--------------

= 1 0000 0000

在内存中存储是以byte为单位的,在byte拼接的过程中就演化除了两种形式:little-endian,big-endian

little-endian适用于机器读懂的顺序,它是前边的地址存储低位的byte,后边的地址存储在高位的地址。

安卓的jni中全部采用little-ednain,int i=6,转换为2进制表示为0000 0110 0000 0000 0000 0000 0000 0000

在内存中的表示:1

2

3

4

5

6+-----------------+---------------+---------------+---------------+

|0|0|0|0|0|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0| 0 |0|0|0|0|0|0|

+-----------------+---------------+---------------+---------------+

^

|

符号位

big-endian适用于人类读的顺序,前边的地址存储高位的byte,后边的地址存储低位的地址。

java中int类型的存储方式是big-endian,int i=6,转换为2进制表示为0000 0000 0000 0000 0000 0000 0000 0110,

在内存中的表示:1

2

3

4

5

6+-----------------+---------------+---------------+---------------+

| 0 |0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|1|0|

+-----------------+---------------+---------------+---------------+

^

|

符号位

同样对于int i=-6的计算也类似于byte的计算,先取反码再加1,然后排序:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15big-endian:

+-----------------+---------------+---------------+---------------+

| 1 |1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|0|1|0|

+-----------------+---------------+---------------+---------------+

^

|

符号位

little-endian:

+-----------------+---------------+---------------+---------------+

|1|1|1|1|1|0|0|1|0|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1| 1 |1|1|1|1|1|1|

+-----------------+---------------+---------------+---------------+

^

|

符号位

利用位运算来进行大小端转换

接下来讲一下如何在byte[]和int之间转换1

2int num=1505;

byte[] result=new byte[4];

这样的一个整数,在java内存以二进制形式表示为:1

2

3+-----------------+---------------+---------------+---------------+

|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|1|1|1|1|0|0|0|0|1|

+-----------------+---------------+---------------+---------------+1

2

3

4byte[0]=00000000=0;

byte[1]=00000000=0;

byte[2]=00000101=5;

byte[3]=11100001=-31;

这里只说一下最后一个字节是如何算出来的,11100001是一个负数,负数是原码取反加1,想获得源码需要先减1,得到11100000,再取反码得到00011111,计算可得是31,由于是负数所以取-31。

这样就完成了int到byte[]的转换。

想要byte[]转int只需将数组拼接起来即可。在操作的过程中要注意一点,byte执行位运算会自动转换为int型,并且补前边的位的时候是自动补上的符号位。

比如前边的result转换为int后如下:1

2

3

4byte[0]=00000000 00000000 00000000 00000000;

byte[1]=00000000 00000000 00000000 00000000;

byte[2]=00000000 00000000 00000000 00000101;

byte[3]=11111111 11111111 11111111 11100001;

这个时候我们就需要使用最常见的&0xff来处理前边的符号位了。

首先写一个简单的程序来将int转换为big-endian的byte:1

2

3

4

5

6

7private static byte[] intToByteBig(int i) {

byte[] result = new byte[4];

result[0] = (byte) (i >> 24 & 0xff);

result[1] = (byte) (i >> 16 & 0xff);

result[2] = (byte) (i >> 8 & 0xff);

result[3] = (byte) (i >> 0 & 0xff);

return result;

假如要转换成little-endian,只需要调整字节排放顺序:1

2

3

4

5

6

7private static byte[] intToByteLittle(int i) {

byte[] result = new byte[4];

result[3] = (byte) (i >> 24 & 0xff);

result[2] = (byte) (i >> 16 & 0xff);

result[1] = (byte) (i >> 8 & 0xff);

result[0] = (byte) (i >> 0 & 0xff);

return result;

同理我们可以得出一个方法来让byte转int:1

2

3

4

5

6

7private static int (byte[] b){

int one = ((int) b[0] & 0xff) << 24;

int two = ((int) b[1] & 0xff) << 16;

int three = ((int) b[2] & 0xff) << 8;

int four = ((int) b[3] & 0xff) << 0;

return one + two + three + four;

}1

2

3

4

5

6

7private static int littleByteToInt(byte[] b){

int one = ((int) b[3] & 0xff) << 24;

int two = ((int) b[2] & 0xff) << 16;

int three = ((int) b[1] & 0xff) << 8;

int four = ((int) b[0] & 0xff) << 0;

return one + two + three + four;

}

关于这个工具类我上传到了我的github,有需要的朋友可以直接复制粘贴。

关于ByteBuffer的使用

其实上边的内容理解了原理就可以了,在java中已经有封装好的类来实现真正的big-endian和little-endian的转换。

ByteBuffer中有一个方法是order(),这个方法接收一个参数,这个参数就可以来进行大小端转换:BIG-ENDIAN 在ByteBuffer中以big-endian形式存储

LITTLE-ENDIAN 在ByteBuffer中以little-endian形式存储

看一段示例代码1

2

3

4

5

6public static short littleByteToShort(byte[] data){

if (data.length != 2) {

throw new UnsupportedOperationException("the byte length is not 2");

}

return ByteBuffer.allocate(data.length).order(ByteOrder.LITTLE_ENDIAN).put(data).getShort(0);

}

这个方法是little-endian转换为short。ByteBuffer.allocate(data.length)分配了2个字节的byteffuer,order(ByteOrder.LITTLE_ENDIAN),让bytebuffer以小端形式存储,put(data)将字节数组存入bytebuffer中,getShort(0)将bytebuffer读取为short类型,注意参数0必须传入,因为put()方法将指针移动了两个字节,所以传入0是从index为0的地方读取一个short。否则会报越界问题。

关于int和long类型的转换依然如此,如果是big-endian,则order()方法需传入参数BIG-ENDIAN。1

2

3public static byte[] shortToLittleByte(short data) {

return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(data).array();

}

这个比较简单,类似于byte[]转short,只是putshort后要调用array来转换为byte[]。

最后也附上这个简单的方法地址,欢迎赋值粘贴:(测试已通过)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值