Posts java基本类型的理解
Post
Cancel

java基本类型的理解

前提补充

  1. 位移运算计算机中存的都是数的补码,所以位移运算都是对补码而言的
  2. 左移<< , 右补0
  3. 有符号右移>> 左补符号位,即:如果符号位是1 就左补1,如果符号位是0 就左补0
  4. 无符号右移>>> ,统一左补0
  5. 原码反码补码关系
    • 正数下,原码=反码=补码
    • 负数下,原码=反码除符号位不变,其余全部取反,补码=反码+1
  6. 计算机中统一用补码的原因
    • 使用补码,可以将符号位和其它位统一处理
    • 减法也可按加法来处理
  7. Java语言提供了八种基本类型
    • 整数型 byte , short , int ,long
    • 浮点型 float ,double
    • 字符型 char
    • 布尔型 boolean

1. byte

  1. 长度8位,有符号的
  2. 最大127(27-1),最小-128(-27
    1
    2
    
    public static final byte   MIN_VALUE = -128;
    public static final byte   MAX_VALUE = 127;
    

如果超过byte的范围的情况下

1
2
3
4
5
6
7
8
9
int i1 = 1234;
byte b1 = (byte) i1;
System.out.println("i1="+i1);
System.out.println("b1="+b1);

int i2 = 2167;
byte b2 = (byte) i2;
System.out.println("i2="+i2);
System.out.println("b2="+b2);

输出结果如下:

1
2
3
4
i1=1234
b1=-46
i2=2167
b2=119

实际上就是把i1强制类型转换到byte类型,而int是32位,byte仅有8位,因此byte获得是int的低8位,把十进制1234转换成二进制如下

1
2
dec = 1234
bin = 0000 0000 0000 0000 0000 0100 1101 0010 

按照byte获得低8位来规则来看,而byte的最高位是符号位,最高位是1,为负数,并二进制补码表示的整数,原码取反得反码再加1得到补码

1
2
3
4
5
1. 低八位 1101 0010
2. 取反码 1010 1101 
3. 取补码 1010 1110
4. 1010 1110首位是符号位,首位是符号位等于1为负数,把010 1110转成十进制就是46
5. 最后得到b1打印出来是-46

再来看一下b2

1
2
dec = 2167
bin = 0000 0000 0000 0000 0000 1000 0111 0111‬

同样的是先取低8位,首位是0,为正数,那么原码等于反码等于补码

1
2
3
4
1. 低八位 0111 0111
2. 取反码 0111 0111
3. 取补码 0111 0111
4. 最后b2打印出来就是119

2. int

  1. 长度32位,有符号的
  2. 最小值0x80000000(-231),最大值0x7fffffff(231-1)
    1
    2
    
    public static final int   MIN_VALUE = 0x80000000;
    public static final int   MAX_VALUE = 0x7fffffff;
    

    把int类型通过byte保存

    int是32位,需要4个byte来保存,那么通过用byte数组分别保存一个int的数据,通过上面的分析,byte保存int的低8位,那么通过右移符把要需要保存的部分移动到低8位即可,然后通过左移符把byte移回到原来的位置,还原int的数据, 代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
            final int i3 = 1281231275;
            System.out.println(Integer.toBinaryString(i3));
            byte[] buff = new byte[4];
            buff[0] = (byte) i3 ;
            buff[1] = (byte) (i3 >> 8);
            buff[2] = (byte) (i3 >> 16);
            buff[3] = (byte) (i3 >> 24);
            for (int i= buff.length-1; i >=0 ;i-- ){
                System.out.print( buff[i] +" ");
            }
            System.out.println("");
            int i3b = (buff[3] << 24 ) +
                      (buff[2] << 16 ) +
                      (buff[1] << 8  ) +
                      (buff[0]);
            System.out.println(Integer.toBinaryString(i3b));
    

    输出结果和预期是一样的

    1
    2
    3
    
    1001100010111100000100110101011
    76 94 9 -85 
    1001100010111100000100110101011
    

    那么当int的是负数的情况下,用如上代码,把i3改成-9,那么以下的输出的结果明显不对

    1
    2
    3
    
    11111111111111111111111111110111
    -1 -1 -1 -9 
    11111110111111101111111011110111
    

    先来计算下当int i3 = -9的时候二进制的表示

    1
    2
    3
    
    原码 1000 0000 0000 0000 0000 0000 0000 1001
    反码 1111 1111 1111 1111 1111 1111 1111 0110
    补码 1111 1111 1111 1111 1111 1111 1111 0111
    

    这个时候,只右移动到低8位转成byte保存情况下,

    1
    2
    3
    4
    
    1111 1111 
    1111 1111 
    1111 1111 
    1111 0111 
    

    这个时候把1111 0111和1111 1111转回原码分别为1000 1001和1000 0001,和再转换成十进制就和打印出来的一样-9和-1

再举例说明,如下

1
2
3
4
5
6
byte a = -1; //原码1000 0001 ->反码 1111 1110 ->  补码1111 1111
int i = a;
int i2 = i << 8;
System.out.println(Integer.toBinaryString(i));
System.out.println(Integer.toBinaryString(i2));
System.out.println(i2);

输出结果

1
2
3
11111111111111111111111111111111
11111111111111111111111100000000
-256

再把修改a=1;输出结果

1
2
3
1
100000000
256
  1. 当byte是负数的时候转成int,jvm会在左边自动用1补位,当为正数的时候,jvm会在左边自动用0补位
  2. 虽然从byte转成int的时候是可以保证十进制的一致性,毕竟int的有效范围比byte更大,但现在我们要是保证在内存中补码的一致性,才能把int转成byte再还原,而在byte是负数的情况下,左移后的,右边补0,再转成int,左边补1,这里就已经改变了补码了,最后4个byte相加,必然不能得到原来的int

那么正确的做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
        public static void main(String[] args) {
            final int i3 = -9;
            System.out.println(add0to32 (Integer.toBinaryString(i3) ));
            byte[] buff = new byte[4];
            buff[0] = (byte) i3 ;
            buff[1] = (byte) (i3 >> 8 );
            buff[2] = (byte) (i3 >> 16 );
            buff[3] = (byte) (i3 >> 24 );
            int b3 = (buff[3] & 0xff) << 24;
            int b2 = (buff[2] & 0xff) << 16;
            int b1 = (buff[1] & 0xff) << 8;
            int b0 = (buff[0] & 0xff) ;
            System.out.println(add0to32 ( Integer.toBinaryString(b3) ));
            System.out.println(add0to32 ( Integer.toBinaryString(b2) ));
            System.out.println(add0to32 ( Integer.toBinaryString(b1) ));
            System.out.println(add0to32 ( Integer.toBinaryString(b0) ));
            int i3b = b3+ b2 + b1 + b0 ;
            System.out.println(add0to32 ( Integer.toBinaryString(i3b) ));
            System.out.println(i3b);
        }

        private static String add0to32(String s){
            StringBuffer buffer = new StringBuffer();
            if(32>s.length()){
                for(int last=0 ;  last < 32-s.length() ; last++){
                    buffer.append("0");
                }
            }
            String a = buffer.append(s).toString();
            StringBuffer buffer1 = new StringBuffer();
            for (int index=0 ; index < 8 ; index++){
                buffer1.append(a.substring(index*4,index*4+4) + " ");
            }
            return  buffer1.toString();
        }

输出结果如下

1
2
3
4
5
6
7
    1111 1111 1111 1111 1111 1111 1111 0111 
    1111 1111 0000 0000 0000 0000 0000 0000 
    0000 0000 1111 1111 0000 0000 0000 0000 
    0000 0000 0000 0000 1111 1111 0000 0000 
    0000 0000 0000 0000 0000 0000 1111 0111 
    1111 1111 1111 1111 1111 1111 1111 0111 
    -9
  1. 重点在于左移前进行&0xff操作,初看0xff就8位,然而0xff大于byte的范围了,当int b0 = (buff[0] & 0xff);的时候,0xff就是一个int类型并且把buff[0]转成int,实际计算如下
    1
    2
    3
    4
    5
    
    1111 1111 1111 1111 1111 1111 1111 0111 
    &
    0000 0000 0000 0000 0000 0000 1111 1111
    =
    0000 0000 0000 0000 0000 0000 1111 0111
    

    这个&0xff就是类似于substring的操作,截取了低8位,左边全部都是补0,就一个整数,在左移后,还不够32位,左边也是补0,保证了补码二进制的一致性,最后四个byte加一起就能还原int

3. long

  1. 64位,有符号
  2. 最大值263-1,最小值-263
    1
    2
    
    public static final long MIN_VALUE = 0x8000000000000000L;
    public static final long MAX_VALUE = 0x7fffffffffffffffL;
    

把long类型通过byte保存

思路上和int类型通过byte保存时候一样的,但是有些小细节,long是64位,所以byte数组的长度得是8,在进行buff[index]&0xff这一步的时候,要在0xff前加上(long)进行强制类型转换,因为0xff默认是int类型,如果对buff[index]&0xff强制类型转换成long,当是负数的时候,就不能保证补码的一致性了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    public static void main(String[] args) {
        final long i3 = 8991123123123123123L;
        System.out.println(Long.toBinaryString(i3));
        System.out.println(add0to64(Long.toBinaryString(i3)));
        byte[] buff = new byte[8];
        for (int index = 0 ;index <8; index++){
            buff[index] = (byte) (i3 >> (index*8));
        }
        long toto = 0L;
        for (int index = 7 ;index >=0; index--){
            //注意这里,先把0xff先强制类型转换成long
            long l = (buff[index] &  (long)0xff)  << (index * 8);
            toto = toto +l;
            System.out.println( add0to64(Long.toBinaryString(l)));
        }
        System.out.println(toto);
    }
    private static String add0to64(String s) {
        StringBuffer buffer = new StringBuffer();
        if (64 > s.length()) {
            for (int last = 0; last < 64 - s.length(); last++) {
                buffer.append("0");
            }
        }
        String a = buffer.append(s).toString();
        StringBuffer buffer1 = new StringBuffer();
        for (int index = 0; index < 16; index++) {
            buffer1.append(a.substring(index * 4, index * 4 + 4) + " ");
        }
        return buffer1.toString();
    }

4. short

  1. 16位,有符号
  2. 最大值215-1,最小值-215
    1
    2
    
    public static final short   MIN_VALUE = -32768;
    public static final short   MAX_VALUE = 32767;
    

    把short类型通过byte保存

    思路和之前如出一辙,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     public static void main(String[] args) {
         short s = -11117;
         byte[] buff = new byte[2];
         buff[0] = (byte) s;
         buff[1] = (byte) (s>>8);
         short s1 = (short) (buff[0] &(short)0xff) ;
         short s2 =  (short) ((buff[1] & (short)0xff) <<8);
         System.out.println(s1+s2);
     }
    

在整类型之间的转换的思路都是差不多的,位数大的转成位数小的,都是截取低位后再右移动,而位数小的还原位数大的,就要记得保持补码的一致性

5. float和double

float

  1. 单精度,32位,符合IEEE754标准的浮点数
    1
    2
    
    public static final float MAX_VALUE = 0x1.fffffeP+127f; // 3.4028235e+38f
    public static final float MIN_VALUE = 0x0.000002P-126f; // 1.4e-45f
    

    double

  2. 是双精度,64 位,符合 IEEE754标准的浮点数
    1
    2
    
    public static final double MAX_VALUE = 0x1.fffffffffffffP+1023; // 1.7976931348623157e+308
    public static final double MIN_VALUE = 0x0.0000000000001P-1022; // 4.9e-324
    

    float和double都是可能会丢失精度的

    丢失精度的问题先要提到讲十进制和二进制之间的转换,整数部分不影响精度丢失问题,问题的关键在于小数部分 例如0.625转成二进制(注意:是从上往下取)

1
2
3
0.625 * 2 = 1.25 -- 1
0.25 * 2 = 0.50 -- 0 
0.5 * 2 = 1.0  -- 1

那么十进制0.625的二进制为0.101 再举例0.62

1
2
3
4
5
6
0.62 * 2 = 1.24 -- 1
0.24 * 2 = 0.48 -- 0
0.48 * 2 = 0.96 -- 0
0.96 * 2 = 1.92 -- 1
0.92 * 2 = 1.84 -- 1
...

十进制0.62转成二进制后0.10011110101110000101000111101011100001010001111011是个无穷的,那么就会有丢弃尾部,肯定就和原来不一致,导致精度丢了

计算机中用IEEE754标准来表示小数,但IEEE754并没有解决小数无法精确表示的问题 float的二进制用科学计数法表示,即float f=1.m*2^n , float在内存中是32位,4字节,其中最高位是符号位,1到8是指数位n,剩下的9到31是数值位m

 符号位指数位n数值位m
单精度float01~89~31
单精度double01~1112~63

float和double在内存中的二进制数

例如float f=8.25f;,先8.25转成二进制,整数部分为1000,小数部分为0.01,整体为1000.01,科学计数法1.00001*2^3,小数点后的00001就是数值位

1
0000 1000 0000 0000 0000 000

指数是3,不过这里不能直接把3转成二进制填入指数位,而是要用位移存储,需要把n+127再转成二进制放入指数位

1
3+127 -> 1000 0010

8.25f在内存中表示为

1
0 1000 0010 0000 1000 0000 0000 0000 000

java代码验证

1
2
3
4
public static void main(String[] args) {
    float f = 8.25f;
    System.out.println(Integer.toBinaryString(Float.floatToIntBits(f)));
}

主要是Float.floatToIntBits,floatToIntBits这就是把float的IEEE754格式的二进制看成int的,再直接用Integer.toBinaryString转成二进制就得到了float在内存的二进制

其实用cpp来说明会更加直接好理解,先定义一个共用体,包含成员float f和unsigned int i,共用体允许在相同的内存位置存储不同的数据类型,也就是说f和i的内存地址是一样的,而float和int都是32位,那么先对f赋值,内存里保存的就是f的二进制,那么就可以通过i直接右移后&1截取最低位,再按照顺序打印出来即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<iostream>
using namespace std;
  
union ufloat{
    float f;
    unsigned int i;
};

int main(){
  ufloat u;
  u.f = 8.25f;
  unsigned int i=1;
  printf("u.i=%u\n",u.i);
  for(int index=31;index>=0;index--){
    unsigned int out = ( u.i >> index ) & i;
    printf("%d",out); 
  }
  printf("\n");
  return 0;
}

例如double d = 89.125;,整数部分89的二进制1011001,小数部分0.001,合起来为1011001.001,IEEE754标准的科学计数法表示为1.011001001*2^6

1
2
3
4
0.125 * 2 = 0.250 -- 0
0.250 * 2 = 0.5 -- 0
0.5 * 2 = 1.0 -- 1
```‬

符号位为0,指数为1023+6=1029–>10000000101,数值位011001001后面补0足到52位

1
0 10000000101 011001001....

在cpp上和float一样的思路,代码如下,因为double是64位,所以选择了同样是64位的long来更方便的读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;

union ddouble{
    double d;
    unsigned long l;
};

int main(){
    ddouble t;
    t.d = 89.125;
    unsigned long os=1l;
    for(int index=63;index>=0;index--){
        unsigned int out = ( t.l >> index ) & os;
        printf("%d ",out); 
    }
    printf("\n");
    return 0;
}

6. char

  1. char在java1.8中16位,2字节
    1
    2
    
    public static final char MIN_VALUE = '\u0000';
    public static final char MAX_VALUE = '\uFFFF';
    
1
2
3
4
char c = 'A';
System.out.println(c);
int i = c & 0xffff;
System.out.println(Integer.toBinaryString(i));

其实char就是保存的ASCII码,可以直接强转成int,打印出来就是在内存的存储

This post is licensed under CC BY 4.0 by the author.