基础知识
运算符顺序
boolean 一位
byte 8位 一个字节
short char 2个字节
int float 4个字节
double long 8字节
java是unicode编码,一个char可以占两个字节,可以存储一个汉字。一个String看编码 如果是utf-8 要看有几个字母或者几个汉字,一个英文字母就是一个字节,一个汉字就是3个字节,以此类推计算。
我们知道,计算机是以补码的形式存放数值型数据,正数的补码,是其本身。负数的补码是对应正数的原码取反,再加1。所以负数对应正数原码的一种正确求法应该是:应该是先减1 再取反
-9对应正数的原码为00001001,因为是负数,补码为最高位置1,其余取反也就是11110110,然后在最低位加1即即11110111。反过来求-9对应正数9的源码即 先-1为 11110110再取反为00001001 (最高位始终为0)
若对char,byte 或者short 进行移位处理,那么在移位进行之前,它们会自动转换成一个int(32位),这个byte型会先自动补全到32位(即一个int型),再进行移位操作。
举个例子:一个byte型的-1,在内存中的补码是八个1:11111111,当我们进行移位时,它会进行补全,而且是有符号位的补全,再左移8位,
所以最后结果是:11111111 11111111 11111111 00000000,
(上例中我们左移8位,需要将11111111 11111111 11111111 00000000去掉前面的那些1,只保留次低位上的1,即为:0000000000000000 11111111 00000000)。
&运算小技巧:1不影响与运算, 如一个int类型的数值 i, 与 0x000000ff(0xff),进行&与运算,可以保留int值最后一位字节上的数值,而将前面的数值全部变为0.然后再进行移位运算,就可以很顺利的消除首尾的符号位的影响了。
|运算小技巧: 0不影响或运算,可以快速的对字节进行拼接,有1就为1,但是注意不进位奥,仅仅是当前位,有1就为1.
带符号按位左移 << 带符号右移>> 不带符号右移 >>>
助记: 右侧补0,左侧补1
1.内存中的位运算,都会先转换成int,4个字节进行运算,左侧不足用符号位补齐
2.<< n : 整体向左移动 n 位,右侧每位用0补齐
3.>> n : 整体向右移动 n 位,左侧每位用符号位补齐
4.>>>n : 整体向右移动 n 位,左侧用0补齐
没有<<<n
-9在计算机中存储为11110111(0xf7) java中占4个字节,即0xfffffff7(不够位数的用符号位补充)。
向左左移4位那么,右侧的4个低位用0补充。为ffffff70
向右移4位那么,左侧的4个高位用4个符号位 1补充。为0xffffffff(4个字节32位16进制一个字符如f占4位)
向右移2位那么,左侧的2个高位用2个符号位 1补充。为0xfffffffd
System.out.println("-9向左移4位:"+Integer.toHexString(-9 << 4));
System.out.println("-9向有右移4位:"+Integer.toHexString(-9 >> 4));
System.out.println("-9向有右移2位:"+Integer.toHexString(-9 >> 2));
结果
-9向左移4位:ffffff70
-9向有右移4位:ffffffff
-9向有右移2位:fffffffd
位 & | ^
1.位& : 按位相乘,有0就为0,1不影响与运算
2.位| : 按位相加,有1就是1,0不影响或运算
3.位^ : 相同为0,不同位1
十六进制的表现形式 0x…,与内存中的存储形式无关(内存中是以二进制存储的),16进制一个字符如f占4位。
内存中的位运算,都会先转换成int,4个字节进行运算,左侧不足用符号位补齐:
与或运算的 默认(不强转)都是int类型。即4个字节进行运算,计算结果还是int 。强转为long与long 与 或结果是long 强转为shot与shot 与 或结果是long
例证如下:
public static int toUnsignedInt16(byte[] buffer, int offset, boolean bigEndian) {
if (bigEndian) {
return (0xff & buffer[offset + 1])
| (buffer[offset] << 8);
} else {
return (0xff & buffer[offset])
| (buffer[offset + 1] << 8);
}
}
public static short toInt16(byte[] buffer, int offset, boolean bigEndian) {
if (bigEndian) {
return (short) ((0xff & buffer[offset + 1])
| (buffer[offset] << 8));
} else {
return (short) ((0xff & buffer[offset])
| (buffer[offset + 1] << 8));
}
}
public static char toChar(byte[] buffer, int offset, boolean bigEndian) {
if (bigEndian) {
return (char) ((0xff & buffer[offset + 1])
| (buffer[offset] << 8));
} else {
return (char) ((0xff & buffer[offset])
| (buffer[offset + 1] << 8));
}
}
// long |long ->long
public static long toInt64(byte[] buffer, int offset, boolean bigEndian) {
if (bigEndian) {
return ((((long) buffer[offset]) << 56) |// 向左移位 右侧用0补齐 符号位不用 &0xff
(((long) buffer[offset + 1] & 0xff) << 48) |
(((long) buffer[offset + 2] & 0xff) << 40) |
(((long) buffer[offset + 3] & 0xff) << 32) |
(((long) buffer[offset + 4] & 0xff) << 24) |
(((long) buffer[offset + 5] & 0xff) << 16) |
(((long) buffer[offset + 6] & 0xff) << 8) |
(((long) buffer[offset + 7] & 0xff)));
} else {
return ((((long) buffer[offset + 7]) << 56) |
(((long) buffer[offset + 6] & 0xff) << 48) |
(((long) buffer[offset + 5] & 0xff) << 40) |
(((long) buffer[offset + 4] & 0xff) << 32) |
(((long) buffer[offset + 3] & 0xff) << 24) |
(((long) buffer[offset + 2] & 0xff) << 16) |
(((long) buffer[offset + 1] & 0xff) << 8) |
(((long) buffer[offset] & 0xff)));
}
}
多字节向少字节强转:如当你将一个int型强制类型转换为byte型的时候,最高的三个字节会被砍掉,只留下最低的8位赋值给byte型。高字节被舍去。
少字节类型向多字节强转:如当你将一个byte型强制类型转换为int型的时候,最高位会补符号位。
public class Test {
public static void main(String[] args) {
int a = 60; /* 60 = 0011 1100 */
int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
System.out.println("a & b = " + c );
c = a | b; /* 61 = 0011 1101 */
System.out.println("a | b = " + c );
c = a ^ b; /* 49 = 0011 0001 */
System.out.println("a ^ b = " + c );
/*-61 = 1100 0011 负数存放是以补码形式存放
最高位不变,其余取反再加1 所以求绝对值应该是先减1 除了最高位再取反
00111101=60 */
c = ~a;
System.out.println("~a = " + c );
c = a << 2; /* 240 = 1111 0000 */
System.out.println("a << 2 = " + c );
c = a >> 2; /* 15 = 1111 */
System.out.println("a >> 2 = " + c );
c = a >>> 2; /* 15 = 0000 1111 */
System.out.println("a >>> 2 = " + c );
}
}
大小端
百度百科对大小端模式解释:
因为除了bool byte类型,其他类型都是多余一个字节,所以涉及到字节在内存的排序的问题,即高位在存在低地址还是高地址中,即所谓的大小端问题。实际应用中,大小端应用的地方很多通信协议、数据存储等。如果字节序不一致,就需要转换。不知道大家对数组(如byte数组)进行强制转换成整型数据(或者相反转化)没有?如果你要进行强制转换,肯定要考虑大小端问题。 跟语言和框架 cpu都有关系如,java默认是小端模式。Netty中默认是大端。
先说结论:
0000430: e684 6c4e 0100 1800 53ef 0100 0100 0000
在大端模式下,前32位应该这样读: e6 84 6c 4e ( 假设int占4个字节),和阅读顺序一致。
在小端模式下,前32位应该这样读: 4e 6c 84 e6( 假设int占4个字节)。
cpu与大小端:
1.常见CPU的字节序
大端模式:PowerPC、IBM、Sun
小端模式:x86、DEC。STM32属于小端模式
操作系统与大小端:
不同的操作系统有不同的存放方式,常见的操作系统是小端
通讯协议与大小端:
通讯协议是大端。网络字节序,TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。网络传输中采用的大端标记法,也就是说先传比较高值的数字,就像 12一样,先传10,在权传2,就算丢了后面一个,损失也不是太大。
语言与大小端
ByteOrder.nativeOrder()我在程序运行了一下,显示java是小端
代码如下
try {
unsafe.putLong(a, 0x0102030405060708L);
byte b = unsafe.getByte(a);
switch (b) {
// 阅读顺序一致
case 0x01: byteOrder = ByteOrder.BIG_ENDIAN; break;
case 0x08: byteOrder = ByteOrder.LITTLE_ENDIAN; break;
default:
assert false;
byteOrder = null;
}
} finally {
unsafe.freeMemory(a);
}
大端模式,是指数据的高位字节保存在内存的低地址中,而数据的低位字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高位字节保存在内存的高地址中,而数据的低位字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。和我们的逻辑顺序一致。
简单来说:big-endian 是高位的放在内存低地址处,低位的放在高地址处。
little-endian 是高位的放在内存高地址处,低位的放在低地址处。
如整数127(十进制)在计算机(64位)中大/小端字节序
数组在大端小端情况下的存储
规约:数组的偏移越大,代表地址越高。
下面以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value
Big-Endian: 低地址存放高位,如下:
高地址
---------------
buf[3] (0x78) – 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) – 高位
---------------
低地址
Little-Endian: 低地址存放低位,如下:
高地址
---------------
buf[3] (0x12) – 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) – 低位
--------------
低地址
内存地址 小端模式存放内容 大端模式存放内容
0x4000 0x78 0x12
0x4001 0x56 0x34
0x4002 0x34 0x56
0x4003 0x12 0x78
不同数值类型转换
先了解一下Java中int型与byte型数组之间的相互转换。
首先,我们先来看看int型转换成byte型数组。
我们知道,Java中,一个int型占用4个字节,一个byte型占用1个字节,所以,对于一个int型,我们需要一个长度为4的byte型数组来对其进行存储。
一个int型的4个字节如上图所示,假设用来存储的字节数组为bytes[],且为小端模式,那么,我们可以用bytes[0]存储int型的第一个字节(7位——0位),bytes[1]存储int型的第二个字节(15位——8位),bytes[2]存储int型的第三个字节(23位——16位),bytes[3]存储int型的第四个字节最高位(31位——24位)。具体代码如下:
1.其他类型转byte数组
public static byte[] int2BytesLittleEndian(int integer)
{
byte[] bytes=new byte[4];
// integer>>24 为int型 当你将一个int型强制类型转换为byte型的时候,最高的三个字节会被砍掉,只留下最低的8位赋值给byte型
bytes[3]=(byte) (integer>>24);//右移24位,左侧补符号位,转成byte,保留原来最高8位,放在数组最高的字节中,代表最高位。为小端。
bytes[2]=(byte) (integer>>16);
bytes[1]=(byte) (integer>>8);
bytes[0]=(byte) (integer);
return bytes;
}
public static byte[] int2BytesBigEndian(int integer)
{
byte[] bytes=new byte[4];
bytes[0]=(byte) (integer>>24);//右移24位,转成byte,保留原来最高8位,放在最低的字节中,代表最低位。为大端。
bytes[1]=(byte) (integer>>16);
bytes[2]=(byte) (integer>>8);
bytes[3]=(byte) (integer);
return bytes;
}
System.out.println("小端");
for(byte b:int2BytesLittleEndian(9)){
System.out.println((Integer.toHexString(b)));
}
System.out.println("大端");
for(byte b:int2BytesBigEndian(9)){
System.out.println((Integer.toHexString(b)));
}
输出 小端 9 0 0 0 大端 0 0 0 9
这里需要注意的是,当你将一个int型强制类型转换为byte型的时候,最高的三个字节会被砍掉,只留下最低的8位赋值给byte型。
接下来,我们来看一下byte型数组转换成int型。
2. byte数组转其他类型
计算技巧:
我们可以先将byte数据元素与0xff 先进行按位与运算( & ),再进行移位,来去除前面的符号位,最后进行 或| 运算。byte型数组转换成int型的代码如下:
public static int bytes2IntLittleEndian(byte[] bytes)
{
//如果不与0xff进行按位与操作,转换结果将出错,有兴趣的同学可以试一下。
int int1=bytes[0]&0xff;
int int2=(bytes[1]&0xff)<<8;
int int3=(bytes[2]&0xff)<<16;
int int4=(bytes[3]&0xff)<<24;// byte[3]最高字节代表最高位为小端
return int1|int2|int3|int4;
}
public static int bytes2IntBigEndian(byte[] bytes)
{
//如果不与0xff进行按位与操作,转换结果将出错,有兴趣的同学可以试一下。
int int1=bytes[3]&0xff;
int int2=(bytes[2]&0xff)<<8;
int int3=(bytes[1]&0xff)<<16;
int int4=(bytes[0]&0xff)<<24;// byte[0]最高字节代表最高位为大段
return int1|int2|int3|int4;
}
// 大端9转化
byte[] b=new byte[]{0x00,0x00,0x00,0x09};// 大端的9和阅读顺序一致 高字节存低位 目标转化出来为9
System.out.println("大端转化 "+bytes2IntBigEndian(b));
System.out.println("小端转化 "+bytes2IntLittleEndian(b));
结果: 大端转化 9 小端转化 150994944
其他类型转化 byte[]=>unsighedInt32
我们因为最后在将byte型数组转换成int型的时候,需要对数组元素使用按位或( | )操作,因此,移位结果前面的符号位如果不去除(去除方式是按字节进行与&运算,后边将byte[]转成int会使用),将影响我们的运算,得出一个错误的结果。
public static long toUnsignedInt32(byte[] buffer, int offset, boolean bigEndian) {
if (bigEndian) {
// 低字节在高位
// 1.强转运算符比&优先级高,所以先转成long。将buffer[offset] 1个字节扩展成8字节,,如果是正数补0不用考虑无符号位,如果是负数有符号位扩展位数,例如如果是-1 一个字节为0xff 强转long 带符号补足64位 后为0xffffffffffffffff。
// 2.&0xff 消除了符号位
// 3.<< 24左移24位 对应下边代码
// long a = (long) buffer[offset];
// long b = a & 0xff;
// long c = b << 24;
// long d = (((long) buffer[offset] & 0xff) << 24);
return ((((long) buffer[offset] & 0xff) << 24) |
(((long) buffer[offset + 1] & 0xff) << 16) |
(((long) buffer[offset + 2] & 0xff) << 8) |
(((long) buffer[offset + 3] & 0xff)));
} else {
return ((((long) buffer[offset + 3] & 0xff) << 24) |
(((long) buffer[offset + 2] & 0xff) << 16) |
(((long) buffer[offset + 1] & 0xff) << 8) |
(((long) buffer[offset] & 0xff)));
}
}
public static long toInt64(byte[] buffer, int offset, boolean bigEndian) {
if (bigEndian) {
return ((((long) buffer[offset]) << 56) |// 向左移位 右侧用0补齐 符号位不用 &0xff
(((long) buffer[offset + 1] & 0xff) << 48) |
(((long) buffer[offset + 2] & 0xff) << 40) |
(((long) buffer[offset + 3] & 0xff) << 32) |
(((long) buffer[offset + 4] & 0xff) << 24) |
(((long) buffer[offset + 5] & 0xff) << 16) |
(((long) buffer[offset + 6] & 0xff) << 8) |
(((long) buffer[offset + 7] & 0xff)));
} else {
return ((((long) buffer[offset + 7]) << 56) |
(((long) buffer[offset + 6] & 0xff) << 48) |
(((long) buffer[offset + 5] & 0xff) << 40) |
(((long) buffer[offset + 4] & 0xff) << 32) |
(((long) buffer[offset + 3] & 0xff) << 24) |
(((long) buffer[offset + 2] & 0xff) << 16) |
(((long) buffer[offset + 1] & 0xff) << 8) |
(((long) buffer[offset] & 0xff)));
}
}
public static int toInt32(byte[] buffer, int offset, boolean bigEndian) {
if (bigEndian) {
return (((buffer[offset]) << 24) |// 转成有符号位 最高位 不用考虑符号位影响
((buffer[offset + 1] & 0xff) << 16) |
((buffer[offset + 2] & 0xff) << 8) |
((buffer[offset + 3] & 0xff)));
} else {
return ((((buffer[offset + 3]) ) << 24) |
((buffer[offset + 2] & 0xff) << 16) |
((buffer[offset + 1] & 0xff) << 8) |
((buffer[offset] & 0xff)));
}
}
根据前文 -9用大端表示为 0xfffffff7所以如下输出:
byte[] bValue = new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xf7};// -9
System.out.println("to unsight 32 "+toUnsignedInt32(bValue, 0, true));
System.out.println("to int 32 " +toInt32(bValue, 0, true));
结果
to unsight 32 4294967287 to int 32 -9