当前位置: 首页 > 图灵资讯 > 技术篇> 说说String吧

说说String吧

来源:图灵教育
时间:2023-09-05 11:54:25

Java基础面试题必须有10道Java (qq.com)

如何理解 String 类型值不可变? - 知乎 (zhihu.com)

为什么 String 被设计为不可改变的? (qq.com)

第2篇面试题系列:new String()创建多少对象?腾讯云开发者社区-腾讯云,你不知道吗? (tencent.com)

String、StringBuffer、StringBuilder 的区别

String是不可变类的典型实现,被声明为final class,除hash属性外,其他属性均声明为final。由于它是不可变的,在拼接字符串时会产生许多无用的中间对象,频繁操作会影响性能;

Stringbuffer是线程安全可修改的字符序列,所有修改数据的方法都添加了synchronized;

Stringbuilder是线程不安全的可修改字符序列,但与使用相比 StringBuffer 能获得 10%~15% 左右性能提升;

问:String的不可变体现在哪里?

String不可变非常简单,如下图所示,给出一个现有的字符串"abcd"第二次赋值成"abcedl",重新指向新对象、新地址,而不是在原始内存地址上修改数据。

alt

  • 1:首先,String类是用final关键词修改的,这说明String是不可继承的,从而避免了子类的破坏 String 不可变。

  • 2:value是String类的主要成员[ ]数组,用final装饰。final修改的字段创建后不能改变。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {    /** String的本质是char数组. 用final关键字修改.*/    private final char value[];...}

真的不可改变吗?

value用final装饰,编译器不允许我将value指向堆区的另一个地址。

但是,如果我直接从数组元素开始,我可以在几分钟内完成。

final int[] value={1,2,3};value[2]=100;  ///此时数组已经是{1,2,100}
问:设计成不可变有什么好处?

一、String 不可变的第一个优点是可以使用字符串常量池。

String s1 = "lagou";String s2 = "lagou";s1 = "LAGOU";System.out.println(s2); //输出lagou

s1 和 s2 常量池中的同一个“背后指向”lagou”

alt

假如String 对象是可变的,所以把它们放在一边 s1 从小写“指向对象”lagou修改为大写的“”LAGOU”之后,s2 应该跟着变化,这个时候打印出来的 s2 它也将是资本。这与我们的期望不一致,也无法实现字符串常量池的功能,因为对象的内容可能会不断变化,无法重复使用。

二、用作 HashMap 的 key

String 不可改变的第二个好处是它可以很容易地使用 HashMap (或者 HashSet) 的 key。通常建议将不可变对象作为不可变对象 Hashmap key,比如 String 非常适合作为 HashMap 的 key。

三、缓存 HashCode

String 不可改变的第三个好处是缓存 HashCode。

在 Java 字符串常用于字符串中 HashCode,在 String 类中有一个 hash 代码如下:属性:

/** Cache the hash code for the String */private int hash;

这是一个成员变量,保存了 String 对象的 HashCode。因为 String 它是不可变的,所以一旦对象被创建,HashCode 值不可能改变,所以我们可以 HashCode 缓存起来。在这种情况下,以后每次都想用。 HashCode 不需要重新计算,直接返回缓存 hash 值是可以的,因为它不会改变,所以它可以提高效率,所以它使字符串非常适合使用 HashMap 的 key。

对于其他没有不变性的普通对象,如果你想得到它 HashCode ,每次都要重新计算,相比之下,效率就低了。

四、线程安全

由于不可变的对象必须是线程安全的,我们自然可以保证线程安全,而无需采取任何额外的措施。

由于String是不可改变的,它可以安全地被多个线程共享,这对多线程编程非常重要,避免了许多不必要的同步操作。

String字符串拼接 (+) 发生了什么事?

Java本身不支持运算符重载,+和+=是String类重载的运算符

字符串通过+字符串拼接,本质上是调用StringBuilderappend()该方法实现后,拼接完成toString()得到一个新的String对象返回。

那为什么比StringBuilder性能低呢?

Stringbuilder对象不会在多个字符串操作中重复使用,而是在每个循环中创建一个stringbuilder对象。

String#intern 方法有什么作用?

String.intern() 是一个 native(本地)方法的作用是在字符串常量池中保存指定字符串对象的引用,可分为两种情况:

  • 如果相应的字符串对象的引用保存在字符串常量池中,则直接返回引用。
  • 如果相应的字符串对象的引用没有保存在字符串常量池中,则在常量池中创建引用并返回指向字符串对象的引用。
// 在堆中创建字符串对象”Java“// 将字符串对象”Java“引用保存在字符串常量池String中 s1 = "Java";// 直接返回字符串常量池中的字符串对象”Java“相应的引用String s2 = s1.intern();// 将在堆中单独创建字符串对象String s3 = new String("Java");// 直接返回字符串常量池中的字符串对象”Java“相应的引用String s4 = s3.intern();// s1 和 s2 System指向堆中的同一对象.out.println(s1 == s2); // true// s3 和 s4 指向堆中不同对象的System.out.println(s3 == s4); // false// s1 和 s4 System指向堆中的同一对象.out.println(s1 == s4); //true
在执行下面的句子时,创建了几个字符串对象?
String str1 = "abc";  // 在常量池中

创建0个或1个对象。

1、如果常量池已经存在”abc因此,不会再创建对象,直接将引用赋值给str1;

2、如果常量池中没有“如果常量池”abc然后创建一个对象,并将引用给str1。

String s1 = new String("abc");// 在堆上

会创建 1 或 2 字符串对象。

1、如果字符串常量池中没有字符串对象”abc“引用,它将首先在字符串常量池中创建,然后在堆叠空间中创建,因此它将创建总共 2 字符串对象。

2、如果字符串常量池中存在字符串对象”abc“引用只会在堆中创建 1 “字符串对象”“字符串对象”“字符串对象”abc”。

我们暂时不考虑常量池中是否存在相应的字符串,假设没有相应的字符串。

String str = "abc" + "def";

以上问题涉及到字符串常量重载“+”的问题。当一个字符串由多个字符串常量拼接成字符串时,它必须是字符串常量。字符串常量的“+”号连接Java虚拟机会优化为程序编译期连接后的值。

就上述示例而言,在编译过程中已合并为“abcdef“字符串,因此,只能创建一个对象。临时字符串对象abc和def没有创建,这减轻了垃圾收集器的压力。

String str = "abc" + new String("def");

上述代码Java虚拟机在编译时也会进行优化,创建StringBuilder拼接字符串,实际效果相似:

String s = new String("def");new StringBuilder().append("abc").append(s).toString();

显然,多了一个Stringbuilder对象,那应该是五个对象。

有些学生可能会想,在Stringbuilder最终tostring()之后,“abcdef难道不是常量池存一份吗?

我们来看看这个代码:

@Testpublic void teststring3() {String s1 = "abc";String s2 = new String("def");String s3 = s1 + s2;String s4 = "abcdef";    // 若s1+s2的结果存储在常量池中,所以S3中value的引用应该和S4中value的引用是一样的。System.out.println(s3=s4); // false}

S4显然存在于常量池中,那么S3对应的值存储在哪里呢?显然是在堆对象中。

让我们来看看Stringbuilder的tostring()方法是如何将拼接结果转化为字符串的:

@Overridepublic String toString() {    // Create a copy, don't share the array    return new String(value, 0, count);}

显然,在tostring方法中创建了一个新的string对象,它是由string对象传递数组的构造方法创建的:

public String(char value[], int offset, int count) 

也就是说,String对象的value值直接指向现有数组,而不指向常量池中的字符串。

因此,上述准确答案应该是创建四个字符串对象和一个Stringbuilder对象。