Java中有且只有两种数据类型,一种是基本的数据类型int,double等,除此之外的都是引用数据类型。
接下来介绍一下不同类型的所占内存。介绍之前先回顾一下计量单位。
举例:int的取值范围是 -231到231-1,大家都知道他是由32位二进制数组成,所以就是32/8 = 4字节。
数据类型 | 占用字节/占用位 |
byte | 1/8 |
short | 2/16 |
int | 4/32 |
long | 8/64 |
double | 8/64 |
float | 4/32 |
char | 2/16 |
boolean | 1/8 |
当不同基本类型的对象要进行运算的时候,都会先进行强制类型转换,转成同一类型后再进行计算,且得出的结果也是强制类型转换后的结果。
强制类型转换的优先级如下:
箭头表示可以强制转换成该类型,比如说byte可以强制转换成short类型。
虚线表示在转换类型的时候可能会发生数据丢失的情况。
double a = 3.2;
int b = 4;
System.out.println(a + b); // 7.2,编译器不会报错。
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a); // 0.100000024
System.out.println(b); // 0.099999964
System.out.println(a == b); // false
double c = 2.0;
double d = 1.1;
System.out.println(c-d); // 0.8999999999999999
答案和我们的常识并不相符,首先要先来看一下浮点数是如何存储的。
float(共32bit) | 符号位(1bit) | 指数位(8bit) | 有效数字(23bit) |
double(共64bit) | 符号位(1bit) | 指数位(11bit) | 有效数字(52bit) |
它们具体是使用IEEE754的标准进行数据的存储,因此无论是float还是有效数字更多的double都不能精确表示一个数,如果要精确表示数字的话(比如货币)和数字比较的话,用decimal类型。
这里只是简单介绍一下double和float,如果还想知道关于浮点数更加具体的内容,可以参考这篇文章。
为什么在处理金额时不能用Double和Float,计算机是如何存储浮点数的
https://blog.csdn.net/qq_41872247/article/details/107861608
先介绍一下Unicode字符集,简单来说这是一张表格,为全世界每种语言的每个字符设立了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
然后Unicode最高是支持4字节的字符处理,也就是说无论是哪个国家的哪个字符都可以在32个二进制数以内表示。
正好我们的Java语言中,是采用Unicode字符集,char的长度是2字节。
Q:为什么char明明是2字节,可以存储汉字?
A:在unicode中,所有汉字在两个字节的范围内,都可以用2字节表示。
Q:如果用char存3字节的字符会怎么样(UTF-8编码模式下)?
A:存不进去,会报错。下面举例子来说明。
例:汉字 ‘风’ 在unicode表中的编码号是 2EDB(16进制,一个数就代表4个二进制数,所以是16位)。
在编译器中,可以通过\uXXXX(X代表16进制数)直接以unicode编码方式存入数据。
char c ='\u2EDB';
System.out.println(c); // 风
可以看到,\u101 3 是蓝色的字色,这就表示编译器将其认成了字符,而后面多出来的3则是绿色的字色,就并没有被包括在字符内。
Q:那我无论如何都想存该怎么办?
A:可以用String类型来存储,String采用了UTF-8的编码方式(中文汉字和中文符号用3字节存储,英文用1字节存储,动态长度,可以转成byte数组),可以过编译。
String s = "\uD800\uDD33";
System.out.println(s); // (如果看不见这个字符检查一下浏览器的编码格式)
boolean在内存中到底占几个字节,其实并不是一个确定的值,它取决于你的JVM的情况。
首先,boolean是只需要一个二进制数就可以表示的类型,但是一般在处理数据的时候最小单位不会用bit而会用byte,所以boolean至少是占用1字节的。
而具体占用多少字节并不清楚,有说是boolean在编译后等同int占4字节,boolean数组等同byte数组占1字节,也有说就只占1字节的。说法较多,但是可以肯定是一定是根据你的JVM来决定,JVM的规格会决定boolean占多少字节。
Java中boolean类型到底占用多少个字节?_gaoyong_stone的博客-CSDN博客
https://blog.csdn.net/gaoyong_stone/article/details/79538195
Java中boolean到底占几个字节_Jeff的专栏-CSDN博客
https://blog.csdn.net/jeffhtlee/article/details/7839377
每种基本数据类型都会有一个对应的包装器类型,比如int就对应Integer,double就对应Double,相当于给基本类型披了一个马甲,让基本类型也能变成引用类型。两者的具体区别如下:
//编译可以通过,但是会报空指针异常
Integer a = null;
int b = a;
// 下面的代码无法通过编译
// int a = null
// List array = new ArrayList<>(); // 报错
List<Integer> array = new ArrayList<>(); // 正常编译。
int a = 1;
int b = 1;
System.out.println(a == b); // true
// 我们一般不用这种方式创建包装类,因为包装类有自动装箱和拆箱,下面有写
Integer c = new Integer(a);
Integer d = new Integer(a);
System.out.println(c == d); // false,本质上是两个对象的地址比较,地址不同为false
// 自动装箱
int a = 1;
Integer a1 = a;
// 本质上是 执行Integer a1 = Integer.valueOf(a);
// 自动拆箱
Integer b = 2;
int b1 = b;
// 本质上是 执行int b1 = b.intValue();
顺便一提,由于自动装箱和自动拆箱的存在(包装器类型和基本类型可以互相转换),会出现这样的问题。除了基本类型以外的数据都是引用数据类型。我们常说的对象也是引用数据类型,Java在使用引用类型和基本类型的时候有个决定性的区别就是值传递和引用传递。
// 值传递
public static void main(String[] args) {
int a = 3;
func(a);
System.out.println(a); // 3
}
public static void func(int i){
i = i + 10;
}
// 引用传递
// 还是一个int,做一个简单的封装
class Test{
int a;
}
public class Main {
public static void main(String[] args) {
Test t = new Test();
t.a = 3;
func(t);
System.out.println(t.a); // 13
}
public static void func(Test i){
i.a = i.a + 10;
}
}
想要介绍说清值传递和引用传递的区别,就要先弄清Java对于基本数据类型和引用数据类型的存储方式。
JVM使用栈(stack)和堆(heap)来进行数据的存储,所以接下来说的是程序内存中的堆和栈,并不是数据结构的堆和栈。
区别 | 堆内存(heap) | 栈内存(stack) |
存储对象 | 各种局部变量和方法帧 | 对象和数组 (数组也是对象的一种) |
优点 | 动态分配内存 | 存储速度快 |
缺点 | 存储速度慢 | 存储空间不大 |
线程共享 | JVM中所有线程共享 | 不在多线程内共享 所以每个线程都有一个栈 |
下面举两个例子来说明堆和栈是怎么工作的:
public class Test {
public static void main(String[] args) {
int a = 10;
func(a);
System.out.println(a); // 10
}
public static void func(int i){
i = i + 10;
}
}
public class Test {
int a;
int b;
public static void main(String[] args) {
Test t = new Test();
func(t);
t = null;
}
public static void func(Test i){
i.a = 10;
}
}
String 这个类比较特别,因为它既可以用双引号“”直接赋值,也可以用new String()来创建对象。那么,不是包装类的String为何可以用两种方式创建对象呢?
接下来要从2中的堆和栈的知识点中再添加一个新内容:字符串常量池。
JVM中有有一个叫做常量池的东西。
常量池分为静态常量池和运行时常量池,其中静态常量池就是CLASS文件中的常量池,存在CLASS文件中,而运行时常量池就是JVM在运行的时候,用来存放一些常量数据。
运行时常量池在JDK7之前存放在方法区中,JDK7开始改为存放在堆内存中。
而在这其中,字符串常量池就是运行时常量池中的一部分,主要就是要注意这一部分即可。
当用""直接赋值给String的时候,首先会在字符串常量池中寻找是否有这个字符串,如果有,就将这个字符串的地址赋值给String。如果没有,则会新建一个字符串,将其放到字符串常量池中,再返回它的引用给String。
String str1 = "1";
String str2 = "1";
System.out.println(str1 == str2); // true,两个字符串指向同一个字符串常量。
就算用 + 将字符串分割成多部分,1的结果不会改变,这是Java特有的优化机制。
String str1 = "11";
String str2 = "1" + "1";
System.out.println(str1 == str2); // true, 仍然指向同一地址。
用new String的方式创建对象的时候,还是会和1和2一样,从字符串常量池中寻找是否有这个字符,无论有没有都会新建这个字符串,再将这个字符串复制到堆内存中,最后将堆内存的副本的地址返回给String对象。
String str1 = "1";
String str2 = new String("1");
System.out.println(str1 == str2); // false, 一个指向常量池,另一个指向堆内存。
在赋值的时候,如果同时出现了new String和 " " 时,会用上StringBuilder这个类来进行辅助创建字符串对象。
String a = "1";
String b = "3";
String str1 = "123";
String str2 = "1" + new String("2") + "3";
// 本质String str2 = new StringBuilder("1").append(new String("2")).append("3").toString()
System.out.println(str1 == str2); // false, 一个指向常量池,一个指向堆内存
在赋值的时候,如果是如下图所示的创建对象,会先用上反射来创建一个StringBuilder,然后再通过StringBuilder创建字符串。
String a = "1";
String b = "2";
String str1 = "12";
String str2 = a + b;
// 先用反射创建一个StringBuilder,剩下的同4。
System.out.println(str1 == str2); // false, 一个指向常量池,一个指向堆内存
String本身是一个不可变类,也就是说,当创建一个String了之后,它的内容就会生成在常量池中,不会再变动了。如果你重新赋值一个新的值给String,也只是在常量池中再创建一个新字符串。原来的赋值并不会消失。
String str1 = "1";
str1 = "12";
而StringBuffer就是一个可以变动的字符串,可以对字符串做出操作。
StringBuffer str1 = new StringBuffer("1");
str1.append("2");
最后说一下StringBuilder,StringBuilder就是StringBuffer的高效率非线程同步版。
类 | 描述 |
String | 字符串常量,创建之后不可更改 |
StringBuffer | 字符串变量,创建之后可以修改 有线程同步的特性,大部分方法用synchronized修饰 |
StringBuilder | StringBuffer的非线程同步版 大部分方法没有用synchronized修饰,所以效率高 |
StringBuilder没有对equals方法进行重写,也就是说StringBuilder的equals方法等同于Object的equals方法。
而String的equals方法只有当目标类能够是String类的实例时,才能进行比较。而StringBuffer和StringBuilder并没有继承String类。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
......
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) { // 判断目标类是否能是String类的实例
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
......
}
所以,请回答下面代码的输出内容:
String str1 = "aaa";
StringBuffer str2 = new StringBuffer(str1);
StringBuffer str3 = new StringBuffer("aaa");
System.out.println(str1.equals(str2));
System.out.println(str3.equals("aaa"));
System.out.println(str3.equals(str1);
System.out.println(str3.toString().equals("aaa"));
第一行str2是StringBuffer类,不能转成String类,false。
第二行StringBuffer类的equals方法等同Object的方法,不是同一对象,false。
第三行同理,不是同一对象,false。
第四行,aaa可以转化为String类,对比后数值相等,true。
彻底理解Java中的基本数据类型转换(自动、强制、提升) - Java技术栈 - 博客园
https://www.cnblogs.com/javastack/p/9111750.html
java的char类型只有两个字节为什么可以存储汉字?-阿里云开发者社区
https://developer.aliyun.com/ask/65417?spm=a2c6h.13159736
Java中 float、double使用注意问题 - panda521 - 博客园
https://www.cnblogs.com/chenjfblog/p/7737332.html
面试官:兄弟,说说基本类型和包装类型的区别吧_沉默王二-CSDN博客
https://blog.csdn.net/qing_gee/article/details/101670051
堆和栈的概念和区别_pt666的博客-CSDN博客
https://blog.csdn.net/pt666/article/details/70876410/
深入理解Java中的String(大坑)_String,java_我的书包哪里去了-CSDN博客
https://blog.csdn.net/qq_34490018/article/details/82110578
String、StringBuffer和StringBuilder的区别_幸遇三杯酒好,况逢一朵花新-CSDN博客
https://blog.csdn.net/csxypr/article/details/92378336