本文是我和武哥联合创作,已收录至我们的GitHub,欢迎大家给个Star:https://github.com/nxJava/nx_java 微信搜索:Java学习指南,关注这个专注于Java干货的公众号~
大家都知道, String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)。我们先来看看 String 的源码。 在 Java 8 中,String 内部使用 char 数组存储数据。 在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。 value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。 因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。 如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。 String 不可变性天生具备线程安全,可以在多个线程中安全地使用。 字符串对象可以使用“+”连接其他对象,其中字符串连接是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的,对象转换为字符串是通过 toString 方法实现的。可以通过反编译验证一下: 由上可以看出,Java中使用”+”连接字符串对象时,会创建一个StringBuilder()对象,并调用append()方法将数据拼接,最后调用toString()方法返回拼接好的字符串。那这个 “+” 的效率怎么样呢? 使用“+”连接符时,JVM会隐式创建StringBuilder对象,这种方式在大部分情况下并不会造成效率的损失,不过在进行大量循环拼接字符串时则需要注意。比如: 这样由于大量StringBuilder创建在堆内存中,肯定会造成效率的损失,所以在这种情况下建议在循环体外创建一个StringBuilder对象调用append()方法手动拼接(如上面例子如果使用手动拼接运行时间将缩小到1/200左右)。 与此之外还有一种特殊情况,也就是当”+”两端均为编译期确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好,例如: JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串。 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享。 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。 我们来看个小例子,了解下不同的方式创建的字符串在内存中的位置: 解析: s1和s2: 由于String类重写了equals方法, s3和s4: s3创建了一个新的字符串”1234″,s4是两个新的字符串”12″和”34″通过”+“符号连接所得,根据Java中常量优化机制, “12” 和”34″两个字符串常量在编译期就连接创建了字符串”1234”,由于字符串”1234″在常量池中存在,故直接把”1234″在常量池的地址赋值给s4,所以 s3和s5: s5是由一个变量s1连接一个新的字符串”4″,首先会在常量池创建字符串”4″,然后进行”+“操作,根据字符串的串联规则,s5会在堆内存中创建StringBuilder(或StringBuffer)对象,通过append方法拼接s1和字符串常量”4”,此时拼接成的字符串”1234″是StringBuilder(或StringBuffer)类型的对象,通过调用toString方法转成String对象”1234″,所以s5此时实际指向的是堆内存中的”1234″对象,堆内存中对象的地址和常量池中对象的地址不一致,故 看下JDK8的API文档里的解释: Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。字符串连接是通过StringBuilder (或StringBuffer )类及其append方法实现的。字符串转换是通过方法来实现toString,由下式定义0bject和继承由在Java中的所有类。有关字符串连接和转换的其他信息,请参阅Gosling,Joy 和Steele,Java 语言规范。 不管是常量池还是堆,只要是使用equals比较字符串,都是比较字符串的内容,所以 Java常量优化机制:给一个变量赋值,如果等于号的右边是常量,并且没有一个变量,那么就会在编译阶段计算该表达式的结果,然后判断该表达式的结果是否在左边类型所表示范围内,如果在,那么就赋值成功,如果不在,那么就赋值失败。但是注意如果一旦有变量参与表达式,那么就不会有编译期间的常量优化机制。 s3和s6: 解析: String s0 = “123”; 字符串常量池对象:“123”,1个; 共1个。 String s1 = new String(“123”); 字符串常量池对象:“123”,1个; 堆对象:new String(“123”),1个; 共2个。 String s2 = new String(“1” + “2”); 字符串常量池对象:“12”,1个(Jvm在编译期做了优化,“1” + “2”合并成了 “12”); 堆对象:new String(“12”),1个 共2个。 由于s2涉及字符串合并,我们通过命令看下字节码信息: 得到字节码信息如下: 备注:以上编译结果基于Jdk1.8运行环境 我们可以很清晰看到,创建了一个新的String对象和一个字符串常量”12″, String s3 = new String(“12”) + “3”; 字符串常量池对象:“12”、“3”,2个, 堆对象: new Stringbuilder().append(“12”).append(“3”).toString();转成String对象,1个; 共3个。 我们同样看下编译后的结果: 总结: new String()是在堆内存创建新的字符串对象,其构造参数中可传入字符串,此字符串一般会在常量池中先创建出来,new String()创建的字符串是参数字符串的副本,看下API中关于String构造器的解释: String(String original) 所以new String()的方式创建字符串百分百会产生一个新的字符串对象,而类似于”123″这样的字符串对象则需要在创建之前看常量池中有没有,有的话就不创建,没有则创建新的对象。 “+”操作符连接字符串常量的时候会在编译期直接生成连接后的字符串,若该字符串在常量池已经存在,则不会创建新的字符串;连接变量的话则涉及StringBuilder等字符串构建器的创建,会在堆内存生成新的字符串对象。 以上就是我们给您带来的关于Java字符串的一些知识总结和面试技巧,你学废了吗? 创作不易,如果您喜欢这篇文章的话,请你 + 评论 支持一下作者好吗?您的支持是我创作的源泉哦!喜欢Java,热衷学习的小伙伴可以加我微信: xia_qing2012 ,私聊我可以获取最新Java基础到进阶的全套学习资料。大家一起学习进步,成为大佬!
文章目录
1. 看看源码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; }
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final byte[] value; /** The identifier of the encoding used to encode the bytes in {@code value}. */ private final byte coder; }
2. 不可变有什么好处呢
2.1 可以缓存 hash 值
2.2 String Pool 的使用
2.3 安全性
2.4 线程安全
3. 再来深入了解一下 String
3.1 “+” 连接符
/** * 测试代码 */ public class Test { public static void main(String[] args) { int i = 10; String s = "abc"; System.out.println(s + i); } } /** * 反编译后 */ public class Test { public static void main(String args[]) { //删除了默认构造函数和字节码 byte byte0 = 10; String s = "abc"; System.out.println((new StringBuilder()).append(s).append(byte0).toString()); } }
3.2 “+”连接符的效率
String s = "abc"; for (int i=0; i<10000; i++) { s += "abc"; }
System.out.println("Hello" + "World"); /** * 反编译后 */ System.out.println("HelloWorld");
4. 字符串常量
4.1 为什么使用字符串常量?
4.2 实现字符串常量池的基础
String string1 = "abc"; // 常量池 String string2 = "abc"; // 常量池 String string3 = new String("abc"); // 堆内存
5. String类常见的面试题
5.1 判断字符串s1和s2是否相等
public static void main(String[] args) { String s1 = "123"; String s2 = "123"; String s3 = "1234"; String s4 = "12" + "34"; String s5 = s1 + "4"; String s6 = new String("1234"); System.out.println(s1 == s2); // true System.out.println(s1.equals(s2)); //true System.out.println(s3 == s4); //true System.out.println(s3 == s5); // false System.out.println(s3.equals(s5)); //true System.out.println(s3 == s6); // false }
String s1 = "123";
先是在字符串常量池创建了一个字符串常量“123”,“123”常量是有地址值,地址值赋值给s1。接着声明 String s2=“123”
,由于s1已经在方法区的常量池创建字符串常量”123″,进入常量池规则:如果常量池中没有这个常量,就创建一个,如果有就不再创建了,故直接把常量”123″的地址值赋值给s2,所以s1==s2
为true。s1.equals(s2)
比较的是字符串的内容,s1和s2的内容都是”123″,故s1.equals(s2)
为true。s3==s4
为true。s3==s5
为false。s3.equals(s5)
为true。
String s6 = new String("1234");
在堆内存创建一个字符串对象,s6指向这个堆内存的对象地址,而s3指向的是字符串常量池的”1234″对象的地址,故s3==s6
为false。5.2 创建多少个字符串对象?
String s0 = "123"; String s1 = new String("123"); String s2 = new String("1" + "2"); String s3 = new String("12") + "3";
javac StrTest.java //编译源文件得到class文件 javap -c StrTest.class // 查看编译结果
new String("1" + "2")
相当于 new String("12")
,共创建了2个字符串对象。
可以看到,包括StringBuilder在内,共创建了4个对象,字符串”12″和字符串”3″是分开创建的,所以共创建了3个字符串对象。
初始化新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算