赞
踩
本篇文章我先通过经典面试题,筛选需要观看本篇文章的朋友,然后咱们介绍String的基本特性,通过基本特性就可以找到面试题的答案。最后咱们再深入每个面试题,通过字节码、编译原理、基本特性深入剖析所有的面试题,让大家最终能通过原理理解所有的面试题,而不是死记硬背。
如果你可以全部答对并且是从原理上答对的,那您是真的完全理解String了,如果全部答对但是都是死记硬背或者未全部答对,则通过本篇文章相信您完全可以不用死记硬背就能答对,所有题目。
- import org.junit.Test;
-
- /**
- * @author liuchao
- * @date 2023/3/2
- */
- public class StringTest {
-
- @Test
- public void test1() {
- String str1 = "abc";
- String str2 = "abc";
- System.out.println(str1 == str2);
- }
-
- @Test
- public void test2() {
- String str1 = "abc";
- String str2 = "abc";
- str2 = "efg";
- System.out.println(str1 == str2);
- }
-
-
- @Test
- public void test3() {
- String str1 = "abc";
- String str2 = "abc";
- str2 += "efg";
- System.out.println(str1 == str2);
- }
-
-
- @Test
- public void test4() {
- String str1 = "abc";
- String str2 = str1.replace('a', 'e');
- System.out.println(str1 == str2);
- }
-
-
- @Test
- public void test5() {
- String str1 = "a" + "b" + "c";
- String str2 = "abc";
- System.out.println(str1 == str2);
-
- }
-
-
- @Test
- public void test6() {
- String str1 = "Hello";
- String str2 = "String";
-
- String str3 = "HelloString";
- String str4 = "Hello" + "String";
- String str5 = str1 + "String";
- String str6 = "Hello" + str2;
- String str7 = str1 + str2;
-
- System.out.println(str3 == str4);
- System.out.println(str3 == str5);
- System.out.println(str3 == str6);
- System.out.println(str3 == str7);
- System.out.println(str5 == str6);
- System.out.println(str5 == str7);
- System.out.println(str6 == str7);
-
- String str8 = str6.intern();
- System.out.println(str3 == str8);
- }
-
- }
-
-
- /**
- * @author liuchao
- * @date 2023/3/2
- */
- public class StringExer {
-
- String str = "hello";
- char[] ch = {'a', 'b'};
-
- public void change(String str, char[] ch) {
- str = "string";
- ch[0] = 'e';
- }
-
- public static void main(String[] args) {
- StringExer exer = new StringExer();
- exer.change(exer.str, exer.ch);
- //请问 输出值 分别是什么
- System.out.println(exer.str);
- System.out.println(exer.ch);
-
- }
-
- }
-
-
- /**
- * @author liuchao
- * @date 2023/3/3
- */
- public class Test3 {
-
- @Test
- public void test1() {
- String str = new String("ab");
- //问 new String("ab") 会创建几个对象呢?
- }
-
- @Test
- public void test2(){
- String str = new String("a") + new String("b");
- //问 new String("a") + new String("b"); 会创建几个对象呢?
- }
- }
-
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
①、String:字符串,使用一对""引起来
②、String类被声明为final的,不可继承
③、String实现了Serializable接口:表示字符串是支持序列化的;实现Comparable接口:表示String可以比较大小
④、String在jdk8及以前内部定义了final char[] value用于存储字符串数据。jdk9时改为byte[] 存储字符串数据。
这里说下为什么jdk9要改为byte[]数组
官网解释:https://openjdk.org/jeps/254
官网解释翻译:
动机(为什么要更改)
目前String类的实现将字符存储在一个char数组中,每个字符使用两个字节(16位)。从许多不同的应用中收集到的数据表明,字符串是堆使用的主要组成部分,此外,大多数字符串对象只包含Latin-1(拼音之类的字符,一个拼音等于一个byte,用char存储就有点浪费了)字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部字符数组中有一半的空间没有被使用。
说明
我们建议将String类的内部表示方法从UTF-16字符数组改为字节数组加编码标志域。新的String类将根据字符串的内容,以ISO-8859-1/Latin-1(每个字符一个字节)或UTF-16(每个字符两个字节)的方式存储字符编码。编码标志将表明使用的是哪种编码。
⑤、String:代表不可变的字符序列。简称不可变性。
当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
当对现有字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
当调用String的replace()方法修改指定字符或者字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
⑥、通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值什么在字符串常量池中。
⑦、字符串常量池是不会存储相同内容的字符串的。
String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。
使用-XX:StringTableSize可设置StringTable的长度
在jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize设置没有要求
在jdk7中,StringTable的长度默认值是60013,StringTablesize设置没有要求
在jdk8中,设置StringTable长度的话,1009是可以设置的最小值
在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种。
直接使用双引号声明出来的String对象会直接存储在常量池中。 eg: String test = "test";
如果不是用双引号声明的String对象,可以使用String提供的intern()方法。动态创建字符串放入常量池
Java 6及以前,字符串常量池存放在永久代
Java 7中 Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内
所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。
字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7中使用String.intern()。
Java8元空间,字符串常量在堆
①、常量与常量的拼接结果在常量池,原理是编译期优化
②、常量池中不会存在相同内容的变量
③、只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
④、如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址
代码:
- import org.junit.Test;
-
- /**
- * @author liuchao
- * @date 2023/3/2
- */
- public class StringTempTest {
-
- @Test
- public void test() {
- String str1 = "abc";
- String str2 = "abc";
- System.out.println(str1 == str2);
-
- }
- }
通过字节码指令可以看出,加载的是同一个常量在地址
注:使用插件为jclasslib,可以访问https://blog.csdn.net/u011837804/article/details/129064876 查看使用方式
代码:
- import org.junit.Test;
-
- /**
- * @author liuchao
- * @date 2023/3/2
- */
- public class StringTempTest {
-
- @Test
- public void test() {
- String str1 = "a" + "b" + "c";
- String str2 = "abc";
- System.out.println(str1 == str2);
-
- }
- }
javac编译后的.class文件反编译看下,证明常量与常量的拼接结果在常量池,原理是编译期优化。
代码:
- import org.junit.Test;
-
- /**
- * @author liuchao
- * @date 2023/3/2
- */
- public class StringTempTest {
-
- @Test
- public void test() {
- String str1 = "Hello";
-
- String str3 = "HelloString";
- String str5 = str1 + "String";
-
- System.out.println(str5 == str3);
- }
- }
代码和字节码对比图片
咱们对字节码逐行分析
- # 对应代码11行
- 0 ldc #2 <Hello>
- 2 astore_1
- # 对应代码12行
- 3 ldc #3 <HelloString>
- # 对应代码14行开始
- # 咱们从字节码不难看出,这里先是new StringBuilder() 然后又调用append()方法,
- # 最后调用了toString()方法
- 5 astore_2
- 6 new #4 <java/lang/StringBuilder>
- 9 dup
- 10 invokespecial #5 <java/lang/StringBuilder.<init> : ()V>
- 13 aload_1
- 14 invokevirtual #6 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
- 17 ldc #7 <String>
- 19 invokevirtual #6 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
- 22 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
- 25 astore_3
- # 对应代码14行结束
- 26 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
- 29 aload_3
- 30 aload_2
- 31 if_acmpne 38 (+7)
- 34 iconst_1
- 35 goto 39 (+4)
- 38 iconst_0
- 39 invokevirtual #10 <java/io/PrintStream.println : (Z)V>
- 42 return
咱们在看看StringBuilder方法的toString()方法具体信息
咱们发现toString()方法实际就是new String()对象,咱们知道通过关键字 new创建的对象,对象实例都是存储在堆上的(非字符串常量池的一块区域),是不是就佐证了,上述代码结果为false
通过翻译方法注释,佐证此方法结果是将对象值,在字符串常量池创建一份
a string that has the same contents as this string, but is
guaranteed to be from a pool of unique strings.
翻译:与此字符串具有相同内容,但保证来自唯一字符串池的字符串
通过字节码查看创建了几个对象
相信大家看完上述的理论+佐证,可以很简单的理解面试题,并且很快解答出来,咱们来看看所有题目的答案。
- import org.junit.Test;
-
- /**
- * @author liuchao
- * @date 2023/3/2
- */
- public class StringTest {
-
- @Test
- public void test1() {
- String str1 = "abc";
- String str2 = "abc";
- System.out.println(str1 == str2);//true
- }
-
- @Test
- public void test2() {
- String str1 = "abc";
- String str2 = "abc";
- str2 = "efg";
- System.out.println(str1 == str2);//不变性,所以为 false
- }
-
- @Test
- public void test3() {
- String str1 = "abc";
- String str2 = "abc";
- str2 += "efg";
- System.out.println(str1 == str2);//不变性,所以 false
- }
-
- @Test
- public void test4() {
- String str1 = "abc";
- String str2 = str1.replace('a', 'e');
- System.out.println(str1 == str2);//不变性,所以 false
- }
-
- @Test
- public void test5() {
- String str1 = "a" + "b" + "c";
- String str2 = "abc";
- System.out.println(str1 == str2);//编译优化 所以 true
-
- }
-
- @Test
- public void test6() {
- String str1 = "Hello";
- String str2 = "String";
-
- String str3 = "HelloString";
- String str4 = "Hello" + "String";
- String str5 = str1 + "String";
- String str6 = "Hello" + str2;
- String str7 = str1 + str2;
-
- System.out.println(str3 == str4);//编译优化 所以 true
- System.out.println(str3 == str5);//两个变量存储的位置不一致,所以 false
- System.out.println(str3 == str6);//两个变量存储的位置不一致,所以 false
- System.out.println(str3 == str7);//两个变量存储的位置不一致,所以 false
- System.out.println(str5 == str6);//都未存储在字符串常量池中,所以地址不一致 false
- System.out.println(str5 == str7);//都未存储在字符串常量池中,所以地址不一致 false
- System.out.println(str6 == str7);//都未存储在字符串常量池中,所以地址不一致 false
-
- String str8 = str6.intern();
- System.out.println(str3 == str8);//intern 将str6内容重新在常量池中创建一份,但是常量池中不允许重复值,所以true
- }
-
- }
- /**
- * @author liuchao
- * @date 2023/3/2
- */
- public class StringExer {
-
- String str = "hello";
- char[] ch = {'a', 'b'};
-
- public void change(String str, char[] ch) {
- str = "string";
- ch[0] = 'e';
- }
-
- public static void main(String[] args) {
- StringExer exer = new StringExer();
- exer.change(exer.str, exer.ch);
- System.out.println(exer.str);//hello
- System.out.println(exer.ch);//eb
-
- }
-
-
- }
- /**
- * @author liuchao
- * @date 2023/3/3
- */
- public class Test3 {
-
- @Test
- public void test1() {
- String str = new String("ab");
- //问 new String("ab") 会创建几个对象呢?
- /**
- * 答案:2个对象
- * 对象1:new String()
- * 对象2: 常量池中的"ab"
- */
- }
-
- @Test
- public void test2(){
- String str = new String("a") + new String("b");
- //问 new String("a") + new String("b"); 会创建几个对象呢?
- /**
- * 答案: 6个对象
- * 对象1:new StringBuilder()
- * 对象2:new String("a")
- * 对象3:常量池中的"a"
- * 对象4:new String("b)
- * 对象5:常量池中的"b"
- * 对象6:StringBuilder.toString()底层 又 new String() 了一个对象
- * 这里注意:StringBuilder.toString() 虽然 new 了一个Stirng,
- * 但是 字符串常量池中是没有生成”ab“
- */
- }
- }
最后:希望对大家的面试有帮助,如果有写错的地方,欢迎大家留言指正,谢谢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。