AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

面试Java里面的String

作者: 作者的头像   chxlD ,  2019-10-11 22:44:28 ,  所有人可见 ,  阅读 1083


3


4

面试Java里面的String

可以说String对象在java里面使用最频繁的一个对象类型。String对象作为Java语言中重要的数据类型,是内存中占据空间最大的一个对象。下面看看平时使用时,需要注意的点:

先来一个老生常谈的面试题:

String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
assertSame(str1 == str2);
assertSame(str2 == str3);
assertSame(str1 == str3);

通过三种不同的方式创建了三个对象,再依次两两匹配,每组被匹配的两个对象是否相等?

底层实现

分3个版本来说说:Java6,Java8,Java9

Java6以及之前版本

char[] 偏移量offset 字符数量count 哈希值hash

String对象是通过offset,count两个属性来定位char[]数组,获取字符串。这么做可以高效、快速地共享数组对象,同时节省内存空间,但这种方式很有可能会导致内存泄漏。

Java7到Java8

char[] 哈希值hash

String类中不再有offset和count两个变量了。这样的好处是String对象占用的内存稍微少了些,同时,String.substring()也不再共享char[],从而解决了使用该方法可能导致的内存泄漏问题。

Java9

byte[] coder 哈希值hash

  1. 一个char字符占16位,2个字节。这个情况下,存储单字节编码内的字符(占一个字节的字符)就显得非常浪费。JDK1.9的String类为了节约内存空间,于是使用了占8位,1个字节的byte[]来存放字符串。

  2. 新属性coder的作用是:

在计算字符串长度或者使用indexOf()时,我们需要根据这个字段,判断如何计算字符串长度。coder: 0 ? 1,0:Latin-1(单字节编码),1:UTF-16。如果String判断字符串只包含了Latin-1,则coder属性值为0,反之则为1。

不可变性

不可变性的体现就是关键字 final(之后会一篇文章专门来讲讲这个关键字~~~)

/** java8里面的实现 */
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}   
  1. String类被final关键字修饰,说明 String 类绝不可能被继承了,也就是说任何对 String 的操作方法,都不会被继承覆写;
  2. 变量char[]被final+private修饰了,说明value 一旦被赋值,内存地址是绝对无法修改的;String 也没有开放出可以对 value 进行赋值的方法,所以说 value 一旦产生,内存地址就根本无法被修改;

我们知道类被final修饰代表该类不可继承,而char[]被final+private修饰,代表了String对象不可被更改。

Java实现的这个特性叫作String对象的不可变性,即String对象一旦创建成功,就不能再对它进行改变。

Java这样做的好处在哪里呢?

  1. 保证hash属性值不会频繁变更。最常见的应用就是HashMap的key-value缓存功能。
  2. 字符串常量池的实现。【这个就涉及到开头的面试题】

现在来说一下开头的那个面试题:

在Java中,通常有两种创建字符串对象的方式:1. 字符串常量的方式创建;2. 通过new形式的创建。

String str = “abc”;

如果用这种方法创建,JVM首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。

String str = new String(“abc”);

首先在编译类文件时,”abc”常量字符串将会放入到常量结构中,在类加载时,“abc”将会在常量池中创建;

其次,在调用new时,JVM命令将会调用String的构造函数,同时引用常量池中的”abc” 字符串,在堆内存中创建一个String对象;

最后,str将引用String对象。

此处说一个一直争议的问题【Java是传值还是传引用】,也是一个经典反例:

String str = "hello";
System.out.println(str);
String str = "world";
System.out.println(str);

结果很多人都知道,两次打印的结果不同。所以有人会说str值改变了,为什么我还说String对象不可变呢?

第一:在Java中要比较两个对象是否相等,往往是用==,而要判断两个对象的值是否相等,则需要用equals方法来判断。第二:这里的str只是String对象的引用,并不是对象本身。

对象在内存中是一块内存地址,str则是一个指向该内存地址的引用。所以在刚刚我们说的这个例子中,第一次赋值的时候,创建了一个“hello”对象,str引用指向“hello”地址;第二次赋值的时候,又重新创建了一个对象“world”,str引用指向了“world”,但“hello”对象依然存在于内存中。

再说一遍:str并不是对象,而只是一个对象引用。真正的对象依然还在内存中,没有被改变。

String.intern

String a = new String("abc").intern();
String b = new String("abc").intern();

if (a == b) {
    System.out.print("a == b");
}

输出结果为:a == b

在字符串常量中,默认会将对象放入常量池;

在字符串变量中,对象是会创建在堆内存中,同时也会在常量池中创建一个字符串对象,引用赋值到堆内存对象中,并返回堆内存对象引用。

如果调用intern(),会去查看字符串常量池中是否有等于该对象的字符串;

  • 如果没有,就在常量池中新增该对象,并返回该对象引用;

  • 如果有,就返回常量池中的字符串引用。

堆内存中原有的对象由于没有引用指向它,将会通过垃圾回收器回收。

然后来看看上面的例子:

创建a变量时,先会在堆内存中创建一个对象,同时会在加载类时,在常量池中创建一个字符串对象,在调用intern方法之后,会去常量池中查找是否有等于该字符串的对象,有就返回引用。

创建b字符串变量时,也会在堆中创建一个对象,此时常量池中有该字符串对象,就不再创建。调用intern方法则会去常量池中判断是否有等于该字符串的对象,发现有等于”abc”字符串的对象,就直接返回引用。而在堆内存中的对象,由于没有引用指向它,将会被垃圾回收。所以a和b引用的是同一个对象。

这就是String.intern()节省内存的原因

字符串连接

String str= "ab" + "cd" + "ef";

这行代码首先会生成ab对象,再生成abcd对象,最后生成abcdef对象,从理论上来说,这段代码是低效的。

但实际运行中,我们发现只有一个对象生成,难道理论判断错了?再来看编译后的代码:

String str= "abcdef";

再来看看一个更大的字符串拼接:

String str = "abcdef";

for (int i = 0; i < 1000; i++) {
    str = str + i;
}

上面的代码编译后,你可以看到编译器同样对这段代码进行了优化:

String str = "abcdef";

for (int i = 0; i < 1000; i++) {
    str = (new StringBuilder(String.valueOf(str))).append(i).toString();
}

不难发现,Java在进行字符串的拼接时,偏向使用StringBuilder,这样可以提高程序的效率。

所以平时做字符串拼接的时候,建议还是显示地使用String Builder来提升系统性能。

但是如果在多线程编程中,String对象的拼接涉及到线程安全,可以使用StringBuffer。但是要注意,由于StringBuffer是线程安全的,涉及到锁竞争【synchronized】,所以从性能上来说,要比StringBuilder差一些。

0 评论

App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息