Java中基本数据类型和包装类
Java中的基本数据类型和包装类
1.1 基本数据类型和包装类
基本数据类型 | 大小 | 包装类 |
---|---|---|
int | 4字节 | Integer |
byte | 1字节 | Byte |
short | 2字节 | Short |
long | 8字节 | Long |
float | 4字节 | Float |
double | 8字节 | Double |
boolean | - | Boolean |
char | 2字节 | Character |
1.2 基本数据类型和包装类区别
- 成员变量包装类不赋值就是null,而基本数据类型有默认值不为null
- 包装类型可用于泛型,但是基本数据类型不可以
- 基本数据类型的局部变量都是存储在java虚拟机栈中的局部变量表中,基本数据类型的非静态成员变量存放在Java虚拟机的堆中。包装类型属于对象类型,几乎所有的对象实例都是存储在堆中。
- 相比于包装类型,基本数据类型占用的空间非常小。
1.3 为什么需要包装类
- 将基本数据类型包装起来,使其成为一个对象,同时也可以为其添加属性和方法,丰富基本数据类型的操作
- 用于泛型。基本数据类型是不可以用于泛型的。
1.4 包装类的缓存机制
使用缓存机制的原因:提升性能,对于常用范围的一些数值不需要一直创建对象。
包装类 | 缓存范围 |
---|---|
Byte,Short,Integer,Long | [-128, 127] |
Character | [0,127] |
Boolean | True/False |
- Integer缓存源码:
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)]; // 算出数组中该缓存对象的索引
return new Integer(i);
}
// 直接定义了一个静态缓存内部类
private static class IntegerCache {
static final int low = -128; // 最小值,静态属性,直接通过类名调用
static final int high; // 最大值
static final Integer[] cache; // 存放这些缓存对象
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
VM.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int k = 0; k < c.length; k++)
c[k] = new Integer(j++);
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
其中缓存的高值是可以设置的,通过JVM参数:-XX:AutoBoxCacheMax=200
设置缓存高值大于127才生效,否则还是127
-
Character缓存源码
@HotSpotIntrinsicCandidate public static Character valueOf(char c) { if (c <= 127) { // must cache return CharacterCache.cache[(int)c]; } return new Character(c); } private static class CharacterCache { private CharacterCache(){} static final Character cache[] = new Character[127 + 1]; static { for (int i = 0; i < cache.length; i++) cache[i] = new Character((char)i); } }
没有设置高值的选项
-
Boolean缓存源码
public static final Boolean TRUE = new Boolean(true); // 直接提前创建好对象 /** * The {@code Boolean} object corresponding to the primitive * value {@code false}. */ public static final Boolean FALSE = new Boolean(false); @HotSpotIntrinsicCandidate public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
1.5 自动装箱和拆箱、原理
看一个例子:
public class IntegerTest {
public static void main(String[] args) {
Integer a = 1; // 如何自动装箱
int b = a; // 如何自动拆箱
}
}
通过javap -v IntegerTest.class
查看其字节码文件
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: iconst_1 // 将1入栈
// 调用了Integer的valueOf方法生成包装类对象
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1 // 将上一步的对象存入到Integer中
5: aload_1 // 加载对象
// 调用Integer.intValue方法对其进行拆箱
6: invokevirtual #3 // Method java/lang/Integer.intValue:()I
9: istore_2 // 存入到b中
10: return
LineNumberTable:
line 5: 0
line 6: 5
line 7: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
5 6 1 a Ljava/lang/Integer;
10 1 2 b I
}
综上得到的结论:自动拆箱和装箱的实现是通过java编译器实现的,通过javac编译之后,会自动生成拆装箱的代码
1.6 float和double
1.6.1 浮点数的精度丢失问题
-
浮点数解释
小数点漂浮不定
比如使用科学计数法来表示一个小数,可以有多种方式:
8.345 = 8.345 * 10^0 8.345 = 83.45 * 10^-1 8.345 = 834.5 * 10^-2
可以看到小数点是不确定的,这种使用科学计数法来表示小数的方式就是浮点数。
-
浮点数如何表示数字
V = (-1)^S*M*R^E 其中S为0或者1,0表示正数,1表示负数 M为尾数,83.45这样的数 R是基数,十进制的R就是10 E是指数
1.6.2 底层是如何存储的
表示一个浮点数:
-
浮点数在底层需要转换成二进制
举例说明25.125
-
整数部分二进制:11001(B)
-
小数部分二进制:0.001(B)。具体做法,不断乘2取小数位,整数位为二进制结果
0.125 * 2 = 0.25 -> 取0 0.25 * 2 = 0.5 -> 取0 0.5 * 2 = 1.0 -> 取1 等于1后计算结束
-
二进制对25.125进行科学表示:25.125(D) = 11001.001(B) = 1.1001001 * 2^4(B)
-
根据上式可以确定:符号为S=0,尾数M=1.001001(B),指数E=4(D)=100(B)
-
填充到32bit上。
-
-
指数和尾数的关系
- 指数位越多,尾数位越少,表示范围越大,精度越差;反之,指数位越少,尾数位越多,表示范围越小,但是精度就会更好
- 一个数字的浮点数格式,会因为定义的规则不同,得到的结果也不同,表示的范围和精度也有差异
-
IEEE754浮点数标准
- 单精度浮点数:32位,符号位S占1bit,指数E占8bit,尾数M占23bit
- 双精度浮点数:64位,符号位S占1bit,指数E占11bit,尾数M占52bit
更多细节参考:http://kaito-kidd.com/2018/08/08/computer-system-float-point/
1.7 char如何存储一个字符
1.7.1 unicode字符集
-
字符集是什么:为世界上所有的符号赋予一个独一无二的数字,这些数字就组成了字符集。
-
Unicode的核心就是一个编码字符集,它使用一个十六进制数字表示一个字符,加上前缀U+。比如A的Unicode就是U+0041
-
码点(code point):就是可以在编码字符集中使用的一个数字。Unicode定义的码点有效范围是:U+0000到U+10FFFF
-
补充字符:码点U+在10000到U+10FFFF
为啥叫补充字符嘞
因为Unicode刚开始设计的最大宽度是16位,但是随着字符越来越多,有一些字符的码点超过了16位,因此将超过16位的这些字符叫做补充字符
1.7.2 编码方式
字符集只是给每个字符赋予了一个独一无二的数字,因为每个字符对应的数字的宽度都有所不同,无法进行统一的存储,因此需要使用编码方式来对字符集中的数字进行编码。
编码方式:将一个或多个编码字符集中的数字映射到一个或多个固定宽度的代码单元序列。
代码单元:就是一个固定宽度的编码结果
注意为啥可能会映射到多个固定宽度的代码单元:对于宽度较小的编码方式来说,可能一个代码单元无法表示所有的字符,因此需要多个代码单元来确定字符
-
UTF-32
由于32位足以表示Unicode中所有数字,因此可以简单地直接将Unicode值映射到UTF-32中
缺点:内存浪费
-
UTF-16
Java的char使用的就是UTF-16,此类编码方式可以满足大部分常用字符,但是对于补充字符,需要使用两个代码单元。
-
UTF-8
需要四个代码单元
1.7.3 特殊字符
Java中的UTF-16如何表示补充字符:
分成高替代区域(U+D800到U+DBFF)和低替代区域(U+DC00到U+DFFF)
高替代区域的值是保留值,没有对应的字符。
参考:https://www.oracle.com/technical-resources/articles/javase/supplementary.html
参考:
[1]: http://kaito-kidd.com/2018/08/08/computer-system-float-point/ 计算机系统基础:浮点数
[2]: https://javaguide.cn/java/basis/java-basic-questions-01.html Java面试
[3]: https://www.oracle.com/technical-resources/articles/javase/supplementary.html java中的补充字符