几乎所有的 Java 面试都是以 String 开始的,String 源码属于所有源码中最基础、最简单的一个,对 String 源码的理解也反应了你的 Java 基础功底。

String 是如何实现的?它有哪些重要的方法?

以主流的 JDK 版本 1.8 来说,String 内部实际存储结构为 char 数组,源码如下:

 源码中包含下面几个重要的方法:

1.多构造方法String字符串有以下4个重要的构造方法:

// String 为参数的构造方法
public String(String original) {
this.value = original.value;
this.hash = original.hash;
} // char[] 为参数构造方法
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
} // StringBuffer 为参数的构造方法
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
} //StringBuilder 为参数的构造方法
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

其中,比较容易被我们忽略的是以 StringBuffer 和 StringBuilder 为参数的构造函数,因为这两种数据类型,我们通常都是单独使用的。

若您想了解三者的区别请查阅我的另一篇博文,谢谢。

String、StringBuffer 和 StringBuilder 的区别

2.equals() 比较两个字符串是否相等

源码如下:

 1  public boolean equals(Object anObject) {
2 // "==" 比较的是引用对象的内存地址,对象引用相同直接返回 true
3 if (this == anObject) {
4 return true;
5 }
6 // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
7 if (anObject instanceof String) {
8 String anotherString = (String)anObject;
9 int n = value.length;
10 if (n == anotherString.value.length) {
11 // 把两个字符串都转换为 char 数组对比
12 char v1[] = value;
13 char v2[] = anotherString.value;
14 int i = 0;
15 // 循环比对两个字符串的每一个字符
16 while (n-- != 0) {
17 // 如果其中有一个字符不相等就 true false,否则继续对比
18 if (v1[i] != v2[i])
19 return false;
20 i++;
21 }
22 return true;
23 }
24 }
25 return false;
26 }

String类型重写了Object中的equals()方法,equals()方法需要传递一个Object类型的参数值,在比较时会先通过instanceof判断是否为String类型,,如果不是则会直接返回 false。

如果您想了解instanceof和isInstance的区别和用法还有“==”和equals的区别和使用,请查看以下文章:

instanceof和isInstance的区别

“==”和equals的区别

3. compareTo() 比较两个字符串

compareTo() 方法用于比较两个字符串,返回的结果为 int 类型的值,源码如下:

 1 public int compareTo(String anotherString) {
2 int len1 = value.length;
3 int len2 = anotherString.value.length;
4 // 获取到两个字符串长度最短的那个 int 值
5 int lim = Math.min(len1, len2);
6 char v1[] = value;
7 char v2[] = anotherString.value;
8
9 int k = 0;
10 //对比每一个字符
11 while (k < lim) {
12 char c1 = v1[k];
13 char c2 = v2[k];
14 // 有字符不相等就返回差值
15 if (c1 != c2) {
16 return c1 - c2;
17 }
18 k++;
19 }
20 return len1 - len2;
21 }

从源码中可以看出,compareTo()方法会循环对比所有的字符,当两个字符串中有任意一个字符不相同时,则return char1-char2。比如,两个字符串分别存储的是1和2,返回的值是 -1;如果存储的是 1 和 1,则返回的值是 0 ,如果存储的是 2 和 1,则返回的值是 1。

可以看出 compareTo() 方法和 equals() 方法都是用于比较两个字符串的,但它们有两点不同:

(1)equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数;

(2)equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int。

总结:它们都可以用于两个字符串的比较,当 equals() 方法返回 true 时,或者是 compareTo() 方法返回 0 时,则表示两个字符串完全相同。

4.其他重要方法

length():查询字符串的长度;

split():把字符串分割并返回字符串数组;

trim():去掉字符串首尾空格;

join():把字符串数组转为字符串;

replace():替换字符串中的某些字符;

contains():查询字符串中是否包含另一个字符串;

indexOf():查询字符串首次出现的下标位置;

lastIndexOf():查询字符串最后出现的下标位置;

toLowerCase():把字符串全部转换成小写;

toUpperCase():把字符串全部转换成大写;

String问题延伸

大厂一贯使用的面试策略,从一个知识点入手然后扩充更多的知识细节,对于 String 也不例外,通常还会关联的询问以下问题:

为什么 String 类型要用 final 修饰?(final 修饰的好处?)

请查看文章 -- final关键字

String 的 intern() 方法有什么含义?

它的作用在jdk1.7之后是查看常量池中是否存在和调用方法的字符串内容一样的字符串,如果有的话,就返回该常量池中的字符串,若没有的话,就在常量池中写入一个堆中该字符串对象的一个引用,指向堆中的该对象,并返回该引用。具体实例在下一个问题中讲解;

String 类型在 JVM(Java 虚拟机)中是如何存储的?编译器对 String 做了哪些优化?

String常见的创建方式有两种,直接赋值的方式“String s1="Java";”和“String s2=new String("Java");”的方式,但两者在JVM的存储区域却截不同,在JDK1.8中,变量s1会先去字符串常量池中找字符串“Java”,如果有相同的字符则直接返回常量句柄,如果没有此字符串则会先在常量池中创建此字符串,然后再返回常量句柄;而变量s2 是直接在堆上创建一个变量,如果调用 intern 方法才会把此字符串保存到常量池中,如下代码所示:

句柄说明:在"think in java"这本书里面讲得很好,在那本书里 他们把引用(reference)叫做"句柄"(Handle)

String s1 = new String("Java"); //堆内存
Strings2 = s1.intern();
String s3 = "Java"; // 常量池
System.out.println(s1 == s2); // false,两个对象地址值不一样
System.out.println(s2 == s3); // true

在 JVM 存储的位置,如下图所示:

除此之外编译器还会对 String 字符串做一些优化,例如以下代码:

String s1 = "Ja" + "va";
String s2 = "Java";
System.out.println(s1 == s2);//true

虽然 s1 拼接了多个字符串,但对比的结果却是 true,我们使用反编译工具发现,代码 "Ja"+"va" 被直接编译成了 "Java" ,因此 s1==s2 的结果才是 true,这就是编译器对字符串优化的结果。

方法区的演变

在jdk1.7版本之前,常量池存在于方法区,方法区是堆的一个逻辑部分,他有一个名字叫做非堆

1.7版本把字符串常量池放到了堆中。

而在1.8以后,则是移除了永久代,方法区概念保留,方法区的实现改为了元空间,常量池还是在堆中。

参考好文:

拉勾网课程 -- Java 面试真题及源码

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59

最新文章

  1. RabbitMq 集群搭建
  2. webform 分页
  3. 批量创建AD测试账号
  4. Firefox访问https出现 ssl_error_weak_server_ephemeral_dh_key错误
  5. 【转】你可能不知道的Shell
  6. retain,copy,assign及autorelease ,strong,weak
  7. Overcome the Dilemma of &quot;unlock&quot; and &quot;trust&quot;
  8. Careercup - Microsoft面试题 - 6282862240202752
  9. ASP.NET弹出模态对话框【转】
  10. asp.net下载的方法
  11. CentOS iSCSI客户端使用配置
  12. 【面试】惠普IT电面
  13. HTTP的报文格式解析
  14. Linux学习之chage命令
  15. 操作 IoT 设备内嵌 SQLite
  16. Java 散知识
  17. JDBC oracle 错误总结
  18. Spring Boot消息队列应用实践
  19. 想在已创建的Vue工程里引入vux组件
  20. 人工智能-机器学习之seaborn(读取xlsx文件,小提琴图)

热门文章

  1. Redis数据库简介
  2. 软工项目WordCount
  3. 【Alpha冲刺阶段】Scrum Meeting Daily3
  4. Codeforces Round #682 Div2 简要题解
  5. 学习笔记:四边形不等式优化 DP
  6. tornado 作业 简单首页 登录页 个人中心
  7. JetBrains系列产品使用记录
  8. linux下为什么每次修改完配置文件之后都需要重新加载配置文件
  9. 判断一个对象是否为空?怎么得到一个对象的第几个键名(key)?
  10. matplotlib学习日记(二)----图表组成练习