Java基础类型存储与运算知识

最近发现一个问题,面试者、甚至组员中,在被问及一些计算机基础类型存储知识的时候,也许是大家目前的工作都是比较偏业务,以至于基础知识不太扎实,感觉有必要在这里在夯实一下,以被后人和自己牢记——基础知识或许不是你现在工作最重要的,但却是你未来进步的基石。

 

1. Java中的原码、反码和补码

1.1 原码

原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:

[+1]原 = 0000 0001

[-1]原 = 1000 0001

第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:

[1111 1111 , 0111 1111]  即  [-127 , 127]

 

1.2 反码

反码的表示方法是:

正数的反码是其本身

负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

[+1] = [00000001]原 = [00000001]反

[-1] = [10000001]原 = [11111110]反

 

1.3 补码

补码的表示方法是:

正数的补码就是其本身

负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]补

[-1] = [10000001]原 = [11111110]反 = [11111111]补

 

1.4 负数运算的问题

将符号位参与运算, 并且只保留加法的方法. 首先来看原码:

计算十进制的表达式: 1-1=0

1 – 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

如果用原码表示, 让符号位也参与计算, 对于减法来说, 结果是不正确的.

 

为了解决原码做减法的问题, 出现了反码:

计算十进制的表达式: 1-1=0

1 – 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

用反码计算减法, 结果的真值部分是正确的, 但是会有[0000 0000]原和[1000 0000]原两个编码表示0.

 

于是补码的出现, 解决了0的符号以及两个编码的问题:

1 – 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128。

 

1.5 补码出现的原因

使用补码, 一是为了防止0有2个编码,其次就是为了把减法运算用加法运算表示出来,以达到简化电路的作用(因为有了负数的概念,减法可以换算为加法) 而且还能够多表示一个最低数. 

这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].

因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-2^31, 2^31-1] 因为第一位表示的是符号位, 而使用补码表示时又可以多保存一个最小值.

 

2. 基础数据所占的内存

 

 注意:对于boolean类型,单个时候占用4个字节,在数组中时占用1个字节。

 

3. 大尾端和小尾端

尾端是计算机中数据存储的一种模式。首先由Danny Cohen(1980引入计算机科学界。

应用于大于1个字节的数字的高位和低位在内存中的存放顺序。

大尾端,指的是数字的高位存储在低地址,数字的低位存放在高地址,主要在solaris,Macintosh ,05年之前的mac中使用。

小尾端和大尾端相反,主要在windows,linux中使用。

 

代码示例:

public class EndianTest {
 
	public static void main(String[] args) {
	    byte[] payload = toArray(-1991249);
	    int number = fromArray(payload);
	    System.out.println(number); //-1991249
 
	    System.out.println("按照大尾端来从byte[4]中解出int");
	    int result = 0xFF & payload[0];
	    result <<= 8;
	    result += 0xFF & payload[1];
	    result <<= 8;
	    result += 0xFF & payload[2];
	    result <<= 8;
	    result += 0xFF & payload[3];
	    System.out.println(result); //-1991249
	}
 
	public static  int fromArray(byte[] payload){
	    ByteBuffer buffer = ByteBuffer.wrap(payload);
	    buffer.order(ByteOrder.BIG_ENDIAN);
	    return buffer.getInt();
	}
 
	public static byte[] toArray(int value){
	    ByteBuffer buffer = ByteBuffer.allocate(4);
	    buffer.order(ByteOrder.BIG_ENDIAN);
	    buffer.putInt(value);
	    buffer.flip();
	    return buffer.array();
	}
 
}

 

4. 符号位扩展与进制转换

直接上代码,运行后可以自己想想结果: 

代码示例:

public class SignedUnsignedTest {
 
	public static void main(String[] args) {
		byte x = 127;
		System.out.println("打印一个字节127:" + x); //127
		System.out.println("打印二进制,是正数,高位被扩展为符号位0,Integer.toBinaryString(127):" + Integer.toBinaryString(x)); //1111111(7个1)
		System.out.println("打印十六进制,是正数,高位被扩展为符号位0,Integer.toHexString(127):" + Integer.toHexString(x)); //7f
 
		x += 1;
		System.out.println("已经溢出了!((byte)127)+1:" + x);
		System.out.println("高位被扩展成符号位1,Integer.toBinaryString(127+1):" + Integer.toBinaryString(x)); //11111111111111111111111110000000
		System.out.println("高位被扩展成符号位1,Integer.toHexString(127+1):" + Integer.toHexString(x)); //ffffff80
		System.out.println("保证了高位不被扩展成符号位1,Integer.toHexString((127+1) & 0xff):" + Integer.toHexString(x & 0xff)); //80
 
		System.out.println("0x80:" + 0x80); // 128,java这种字面常亮,不遵循符号位扩展原则,高位全部补0
		System.out.println("0x81:" + 0x81); // 129
		System.out.println("0x82:" + 0x82); // 130
		System.out.println("(byte)0x80:" + (byte)0x80); // -128
		System.out.println("(byte)0x81:" + (byte)0x81); // -127
		System.out.println("(byte)0x82:" + (byte)0x82); // -126
		System.out.println("0x8000:" + 0x8000); // 32768
		System.out.println("(short)0x8000:" + (short)0x8000); // -32768
		System.out.println("0x80000000:" + 0x80000000); // -2147483648
		System.out.println("0x80000000L:" + 0x80000000L); // 2147483648
 
		System.out.println("高位被扩展为了fff...f,所以结果不对,Long.toHexString(0x1000000L + 0xcafebabe):" + Long.toHexString(0x1000000L + 0xcafebabe)); // ffffffffcbfebabe
		System.out.println("高位没有杯扩展为了fff...f,所以结果对,Long.toHexString(0x1000000L + 0xcafebabe):" + Long.toHexString(0x1000000L + 0xcafebabeL)); // cbfebabe
 
		System.out.println("如果是窄类型扩充为宽类型,符号位扩展,但是!!如果是char则扩展0,(int)(char)(byte)-1:" + (int)(char)(byte)-1); //65535
		System.out.println("如果是窄类型扩充为宽类型,符号位扩展,(int)(char)(byte)-1:" + (int)(short)(byte)-1); //-1
 
		char c = 'x';
		int y = c & 0xffff; //等同于int y = c; 如果是char,则零扩展
		System.out.println("char c = 'x';c:" + c);
		System.out.println("int y = c & 0xffff;y:" + y);
 
		byte b = -1;
		System.out.println("有符号位扩展,(short)b" + (short)b); // -1
		System.out.println("无符号位扩展,(short)(b & 0xff)" + (short)(b & 0xff)); //255
 
		System.out.println("符号位扩展后提升为int,高位全部补充了1,而右边的int高位是0,自然不相等,(byte)0x90 == 0x90:" + ((byte)0x90 == 0x90)); // false
		System.out.println("符号位扩展后提升为int,用掩码将其保留原值高位都是0,而右边的int高位是0,二者相等,(byte)0x90 == 0x90:" + (((byte)0x90 & 0xff) == 0x90)); // true
 
		byte[] byteArray = new byte[4];
		byteArray[0] = -1;
		byteArray[1] = 127;
		byteArray[2] = 3;
		byteArray[3] = -128;
		System.out.println(byteArrayToHexString(byteArray)); //ff7f0380
	}
 
	/**
	 * 将字节数组输出为十六进制字符串
	 * @param byteArray
	 * @return
	 */
	public static String byteArrayToHexString(byte[] byteArray) {
		String res = "";
		for (byte b : byteArray) {
			String hex = Integer.toHexString(b & 0xff); 
			hex = (hex.length() == 1) ? "0" + hex : hex;
			res += hex.toLowerCase();
		}
		return res;
	}
 
}

 

3 Comments on this Post.

  1. 旭总,那个boolean类型的上下矛盾了吧。 图片里说是1位,我理解跟bitmap一样,是1byte,为啥下面注释说单个4字节,数组中占1字节。

  2. neo

    你好,java中的boolean在虚拟机规范里面是当做int来对待的,所以是4byte,而boolean数组可以当做1个byte认识。

Leave a Comment.