自己的网站怎么编辑,软件开发工具自考,90平方装修价格明细,html网页制作企业类网站前言 由于发现 String、StringBuilder、StringBuffer 面试的时候会经常问到#xff0c;这里就顺便总结一下#xff1a;本文重点会以这三个字符串类的性能、线程安全、存储结构这三个方面进行分析 ✨上期回顾#xff1a;Java 哈希表 ✨目录 前言 String 介绍 String 的不可变… 前言 由于发现 String、StringBuilder、StringBuffer 面试的时候会经常问到这里就顺便总结一下本文重点会以这三个字符串类的性能、线程安全、存储结构这三个方面进行分析 ✨上期回顾Java 哈希表 ✨目录 前言 String 介绍 String 的不可变性 String 在字符串常量池中的表示 字符串常量池没有该字符串 字符串常量池有该字符串 总结 StringBuilder 与 StringBuffer 效率的比较 线程安全的比较 模拟面试 String 介绍 String 的不可变性 跳转到 String 的实现就会发现 String 类 不能被继承该类被 final 修饰String 类是不可变的value[ ] 被 final 修饰表明 value[ ] 自身的值不能改变String 类可以序列化可以和其他 String 比较其大小实现了 Comparable 接口 通过下述代码你就会发现 String 类中每一个看起来会修改 String 值的方法实际上都是创建了一个全新的 String 对象包含修改后的字符串内容。而最初的 String 对象则丝毫未动 class Test{static String Func(String s){return s.toUpperCase();}public static void main(String[] args) {String str hello world;System.out.println(str);String ret Func(str);System.out.println(ret);System.out.println(str);}
}//输出结果
hello world
HELLO WORLD
hello world 当把 str 传给 Func 方法时实际传递的是引用的一个拷贝。其实每当把 String 对象作为方法的参数时都会复制一份引用而该引用所指的对象其实一直待在单一的物理位置上从未动过 String 在字符串常量池中的表示 先从一段代码开始吧以下这行代码总共创建了几个对象呢
String str new String(Hello); 我想很多人看到会不暇思索的回答“这不是一个吗”其实并不然它创建了两个对象 字符串常量池没有该字符串 如果字符串常量池中没有 Hello 这个字符先在字符串常量池中创建一个 ‘Hello’ 的字符串对象然后再在堆中创建一个 ‘Hello’ 的字符串对象然后将堆中这个 ‘Hello’ 的字符串对象地址返回赋值给变量 str。因此需要创建两个对象。 字符串常量池有该字符串 String str new String(Hello World);String ret new String(Hello World); Java 虚拟机会先在字符串常量池中查找有没有 ‘Hello’ 这个字符串对象如果有就不会在字符串常量池中创建 ‘Hello’ 这个对象了直接在堆中创建一个‘Hello’ 的字符串对象然后将堆中这个 ‘Hello’ 的对象地址返回赋值给变量 str。因此只需要创建一个对象。 注意ret 所指向的字符是 Hello不是HHllo画到最后没存档回不去了将就着看吧 为什么要先在字符串常量池中创建对象然后再在堆上创建呢 由于字符串的使用频率实在是太高了所以 Java 虚拟机为了提高性能和减少内存开销在创建字符串对象的时候进行了一些优化特意为字符串开辟了一块空间 -- 也就是字符串常量池 通常情况下我们会采用双引号的方式来创建字符串对象而不是通过 new 关键字的方式因为 new 会强制创建对象会对资源造成浪费。 如果我们采用双引号创建对象如下图所示
String str1 Hello; String str2 World; Java 虚拟机会先在字符串常量池中查找是否存在该字符串如果存在则不创建任何对象直接返回常量池中的对象引用如果不存在则在常量池中创建该字符串并返回对象引用。这样做的好处是避免了重复创建多个相同的字符串对象减少了内存的开销。 接下来我们来研究一个经典的面试问题 public static void main(String[] args) {String a abc;String b new String(abc);String c new String(abc);String d b.intern();System.out.println(a b);System.out.println(b c);System.out.println(a d);} 请问上述程序打印的结构是什么
// 打印结构为
false
false
true 通过 String a abc 这样创建一个字符串对象时JVM会首先在字符串常量池中寻找这个字符串我们发现 abc 不存在则在常量池中创建该字符串并将 a 指向它。 通过 String b new String(abc) 这样创建字符串时情况就不一样了同样先在字符串常量池中寻找这个字符串我们发现 abc 存在。它会在堆区创建该字符串对象并使 b 指向它同样调用 String c new String(abc) 时也会在堆区再创建一个 String 对象并使 c 指向它。由于我们字符串中 “” 比较的是地址而我们的 b、c 创建的是两个不同的对象所以返回 false。a、b 同理返回 false。 当调用 String d b.intern() 时intern方法该方法为 native 方法会在字符串常量池中查找是否存在该字符串对象如果存在则将 d 指向该常量池中的字符串对象如果不存在则在常量池中创建该字符串并指向它所以 a d 返回 true。 总结
使用双引号声明的字符串对象会保存在字符串常量池中使用 new 关键字创建的字符串对象会先从字符串常量池中找如果没找到就创建一个然后再在堆中创建字符串对象如果找到了就直接在堆中创建字符串对象在存在字符串常量池的前提下使用 new 关键字但是不想创建对象可以使用 intern 方法直接获取字符串常量池的引用 StringBuilder 与 StringBuffer 效率的比较 通过以上内容相信你已经对 String 有一定了解。由于字符串是不可变的所以当遇到字符串的拼接尤其是使用 号操作符的时候就需要考量性能的问题你不能毫无顾虑地生产太多 String 对象对珍贵的内存造成不必要的压力 于是 Java 就设计了两个专门用来解决此问题的 StringBuilder、StringBuffer 类 ~ 可能有人会问 String 能做的事情干嘛还要用 StringBuilder、StringBuffer 呢我们可以对一个字符进行多次拼接查看程序的运行效率如下述代码 public static void main(String[] args) {String s ;long st System.currentTimeMillis();for(int i 0; i 100000; i) {s a;}long ed System.currentTimeMillis();System.out.println(String时间 (ed - st) 毫秒);st System.currentTimeMillis();StringBuilder sb new StringBuilder();for(int i 0; i 100000; i) {sb.append(a);}ed System.currentTimeMillis();System.out.println(StringBuilder时间 (ed - st) 毫秒);st System.currentTimeMillis();StringBuffer sf new StringBuffer();for(int i 0; i 100000; i) {sf.append(a);}ed System.currentTimeMillis();System.out.println(StringBuffer时间 (ed - st) 毫秒);}
代码运行结果
String时间827毫秒
StringBuilder时间1毫秒
StringBuffer时间3毫秒 可以看出在大量对字符串进行连接操作的情况下StringBuilder、StringBuffer 优势非常明显。因为 String 拼接会产生大量对象而 StringBuilder、StringBuffer 无论是创建、拼接、修改、删除都是直接作用于原字符串并不会产生多余的对象。其次 StringBuilder 比 StringBuffer 的效率稍微高一点也是有原因的这就涉及到线程安全问题待会再讲。 线程安全的比较 我们可以来对比一下它们的底层源码再来做分析
//String
public final class Stringimplements java.io.Serializable, ComparableString, CharSequence {private final char value[];...
}//StringBuilder
public final class StringBuilderextends AbstractStringBuilderimplements Serializable, CharSequence
{Overridepublic StringBuilder append(Object obj) {}IntrinsicCandidatepublic String toString() {...}...
}//StringBuffer
public final class StringBufferextends AbstractStringBuilderimplements Serializable, CharSequence
{//方法有synchronized关键字Overridepublic synchronized StringBuffer append(Object obj){...}IntrinsicCandidatepublic synchronized String toString() {...}...
}//AbstractStringBuilder
abstract sealed class AbstractStringBuilder implements Appendable, CharSequence permits StringBuilder, StringBuffer {byte[] value;...
}1 我们可以看到在 String 中value 是被 final 修饰的是不可变的StringBuilder、StringBuffer 都继承于 AbstractStringBuilder 这个类而这个类中 的 value 是可变数组所以我们进行拼接等操作时是直接作用于原字符串实现的这就是效率高的由来。 2 我们观察 StringBuilder、StringBuffer 的 toString、append 方法由于 StringBuffer 操作字符串的方法加了synchronized 进行了同步所以每次操作字符串时都会加锁所以线程安全、但是性能低。这就是 StringBuilder 比 StringBuffer 运行效率略高的原因。 总结
String 类
不可变性一旦创建内容不可改变线程安全由于不可变性String 对象天生线程安全性能频繁的字符串操作会导致大量的对象创建和内存消耗
StringBuilder 类
可变性内容可以被改变非线程安全适用于单线程环境性能比 String 更适合频繁的字符串操作因为不会创建大量的中间对象
StringBuffer 类
可变性内容可以被改变线程安全所有方法都是同步的适用于多线程环境性能由于同步机制性能略低于 StringBuilder 模拟面试 如果HR问你String、StringBuffer、StringBuilder 的区别你会怎么回答 答关于String、StringBuffer、StringBuilder的区别我有四个方面来说 第一个是可变性String 内部的 value 是 final 修饰的所以它是一个不可变的类所以每次修改 String 的值的时候都会产生一个新的对象。而 StringBuffer、StringBuilder 是一个可变类字符串的变更不会产生新的对象。 第二个是线程的安全性因为 String 是一个不可变的类所以它是线程安全的而 StringBuffer 也是线程安全的因为它的每个操作方法中都有一个 synchronized 一个同步关键字StringBuilder 不是线程安全的所以在多线程环境下对字符串进行操作的时候我们应该使用 StringBuffer 否者使用 StringBuilder。 第三个是性能方面String 效率是最低的因为其不可变性导致做字符串的拼接或者修改的时候我们需要创建新的对象以及分配内存其次是 StringBuffer 比 String 的效率更高一点因为它的可变性意味值字符串可以直接被修改最后性能最高的是 StringBuilder 因为 StringBuilder 比 StringBuffer 的性能要高因为 StringBuffer 加了同步锁意味着对性能产生了影响。 第四个是存储方面String 存储在字符串常量池中而 StringBuffer、StringBuilder 则是存储在堆的内存空间。