Java系列文章目录
Write once,Runanywhere.🔥🔥🔥
💥 💥 💥如果你觉得我的文章有帮助到你,还请【关注➕点赞➕收藏】,得到你们支持就是我最大的动力!!!
💥 💥 💥
⚡版权声明:本文由【马上回来了】原创、在CSDN首发、需要转载请联系博主。
版权声明:本文为CSDN博主「马上回来了」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
🚀🚀🚀 新的知识开始喽🚀🚀🚀
文章目录
1.String常量池
1.1 创建对象的思考
下面创建String对象的方式相同吗?
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1==s2);//true
System.out.println(s1==s3);//false
System.out.println(s3==s4);//false
}
为什么s1和s2引用的是同一个对象,而s3和s4引用的不是同一个对象呢?
对于1,3.14,"hello"这种被经常使用的字面常量,为了使程序运行跟块,跟节省空间,Java为这8中数据类型和String类型提供了常量池。
1.2 字符串常量池(Stringable)
字符串常量池在JVM中是StringTable类,实际是一个固定大小HashTable(一种高效用来进行查找的数据结构,后序给大家详细介绍),不同JDK版本下字符串常量池的位置以及默认大小是不同的,其中Java8 在堆中。
1.3 再谈对创建对象的思考
此处在java8上分析
对于直接使用常量创建String类:
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1==s2);//true
}
详细讲解:
直接new常量池一开始没有的对象:
public static void main(String[] args) {
String s1 = new String("world");
}
对于直接new对象:
public static void main(String[] args) {
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3==s4);//false
}
1.4 intern方法
intern方法是一个native方法(native方法是指底层用C++实现的,看不到它的源代码)该方法的作用是手动将创建的String对象添加到常量池中。
public static void main(String[] args) {
char[] chars = {'a','b','c'};
String s1 = new String(chars);
//s1.intern();
String s2 = "abc";
System.out.println(s1==s2);//未使用intern方法 false
}
先看这两行代码:
//字符数组转字符串
char[] chars = {'a','b','c'};
String s1 = new String(chars);
这两行代码执行的结果:
这两行代码执行的过程:
一直到到这两行代码执行结束,常量池都没有添加双引号"“引起的字符串常量,因为s1里没有”"引起的字符串。
整个代码执行完毕:
当执行intern方法时:
public static void main(String[] args) {
char[] chars = {'a','b','c'};
String s1 = new String(chars);
s1.intern();
String s2 = "abc";
System.out.println(s1==s2);// true
}
public static void main(String[] args) {
char[] chars = {'a','b','c'};
String s1 = new String(chars);
//运行到这里s1不在常量池中
//s2在常量池中
String s2 = "abc";
//intern检查常量池,发现常量池里有"abc",不会再将s1引用的对象放入到常量池里,因此这里结果跟没有使用intern方法的情况类似。
s1.intern();
System.out.println(s1==s2);// false
}
1.5 面试题:请解释String类中两种对象实例化的区别
JDK1.8中
- String str = “hello”
只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象。常量池里的对象。
- String str = new String(“world”)
会开辟两块堆内存空间,字符串"world"保存在字符串常量池中,然后用常量池中的String对象给新开辟的String对象赋值。new一个对象、常量池里new一个对象。
- String str = new String(new char[]{‘h’, ‘e’, ‘l’, ‘l’, ‘o’})
先new一个char类型的数组对象,然后在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到String对象中。
2.字符串的不可变性
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
- String类在设计时就是不可改变的,String类实现描述中已经说明了
String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出: - String类被final修饰,表明该类不能被继承。
- value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。
【纠正】网上有些人说:字符串不可变是因为其内部保存字符的数组被final修饰了,因此不能改变。
这种说法是错误的,不是因为String类自身,或者其内部value被final修饰而不能被修改。
【正确】关键在于value被private修饰类外拿不到value的值,因此无法对string进行修改。
为什么 String 要涉及成不可变的?(不可变对象的好处是什么?) (选学)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中
3.字符串修改
- 所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象。比如 replace 方法:
看下面这个代码:
public static void main(String[] args) {
String s = "hello";
s += " world";
System.out.println(s); //并不是在原有的hello空间上接了一个world,而是产生了一个新对象输出:hello world,
}
2.注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。效率低下的原因是因为每次对字符串拼接都会产生新的对象。
下面是String类、StringBuilder类和StringBuffer类分别执行一段代码,StringBuilder类和StringBuffer类的效率是String类的282倍。
public static void main(String[] args) {
//String
long start = System.currentTimeMillis();
String s = "";
for(int i = 0; i < 10000; ++i){
s += i;
}
long end = System.currentTimeMillis();
System.out.println(end - start);//282
//StringBuffer
start = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer("");
for(int i = 0; i < 10000; ++i){
sbf.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);//1
//StringBuilder
start = System.currentTimeMillis();
StringBuilder sbd = new StringBuilder();
for(int i = 0; i < 10000; ++i){
sbd.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);//1
}
4. StringBuilder和StringBuffer
再看这个代码:
public static void main(String[] args) {
String s = "hello";
s += " world";
System.out.println(s); //并不是在原有的hello空间上接了一个world,而是产生了一个新对象输出:hello world,
}
我们看看这段代码的汇编:
我们已经知道了:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。
现在用StringBuilder来还原这段汇编码:
append方法:
toString方法:
StringBuilder s1 = new StringBuilder();
s1.append("hello");
s1.append("hello");
String s = s1.toString();
System.out.println(s);//hellohello
代码执行过程看动图:
当然也可以使用StringBuffer来实现:
StringBuffer s1 = new StringBuffer();
s1.append("hello");
s1.append("world");
String s = s1.toString();
System.out.println(s);
使用StringBuilder与StringBuffer的区别:
面试题:
- String、StringBuffer、StringBuilder的区别
- String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
- StringBuffer与StringBuilder大部分功能是相似的
- StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
- 以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】
String str = new String("ab"); // 会创建多少个对象 2
String str = new String("a") + new String("b"); // 会创建多少个对象 2 + 2 + 2(new StringBuilde)、(new toString)
🌏🌏🌏今天的你看懂这里又学到了很多东西吧🌏🌏🌏
🌔 🌔 🌔下次见喽🌔 🌔 🌔