几乎所有的 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

String -- 从源码剖析String类的更多相关文章

  1. 深入源码剖析String,StringBuilder,StringBuffer

    [String,StringBuffer,StringBulider] 深入源码剖析String,StringBuilder,StringBuffer [作者:高瑞林] [博客地址]http://ww ...

  2. Netty学习笔记(三)——netty源码剖析

    1.Netty启动源码剖析 启动类: public class NettyNioServer { public static void main(String[] args) throws Excep ...

  3. Django Rest Framework源码剖析(六)-----序列化(serializers)

    一.简介 django rest framework 中的序列化组件,可以说是其核心组件,也是我们平时使用最多的组件,它不仅仅有序列化功能,更提供了数据验证的功能(与django中的form类似). ...

  4. 源码学习-String类

    最近在扫描CodeDex时报了一个不能使用String.intern()的字符串来做锁对象的告警,对这个问题有疑问查了些资料,顺便学习一下String类的源码. 1.类定义 String 被final ...

  5. JDK源码之String类解析

    一 概述 String由final修饰,是不可变类,即String对象也是不可变对象.这意味着当修改一个String对象的内容时,JVM不会改变原来的对象,而是生成一个新的String对象 主要考虑以 ...

  6. JDK源码学习--String篇(二) 关于String采用final修饰的思考

    JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JD ...

  7. JDK10源码阅读--String

    jdk源码里对String的介绍: String 是不可变的,一旦被创建其值不能被改变. String buffers 支持可变String. 因为String是不可变的, 所以它们可以被共享. 例如 ...

  8. JDK1.8源码之String

    一.String类型 引用博文连接:  https://blog.csdn.net/ylyg050518/article/details/52352993 一.成员变量 //用于存储字符串 priva ...

  9. 从源码看String,StringBuffer,StringBuilder的区别

    前言 看了一篇文章,大概是讲面试中的java基础的,有如题这么个面试题.我又翻了一些文章看了下,然后去看源码.看一下源码大概能更加了解一些. String String类是final的,表示不可被继承 ...

随机推荐

  1. Linux下netstat命令简单操作

    netstat -t :TCP协议 -u :UDP协议 -l :监听 -r :路由 -n :显示IP地址和端口号 常用: netstat -tlun 查看本机监听的端口 netstat -an 查看本 ...

  2. Acwing 403. 平面

    以一个这个环为基准,剩下的边可以放在圈外,也可以放在圈内,两种状态. 如果两条线段出现了环上意义的交叉即冲突,即不能同时放在圈外/内. 这是典型的 2-SAT 问题,因为关系传递是无向的,即逆命题与原 ...

  3. 最简 Spring IOC 容器源码分析

    前言 BeanDefinition BeanFactory 简介 Web 容器启动过程 bean 的加载 FactoryBean 循环依赖 bean 生命周期 公众号 前言 许多文章都是分析的 xml ...

  4. 开源抓包工具PowerSniff(支持lua,c语言作为脚本实时分析)

    做这个程序的意图是wireshark插件编写复杂(虽然也支持lua),而轻量级的工具如smartsniff,minisniff不支持插件化数据分析,各种工具用下来或多或少不顺手.以前写的外挂也都是手工 ...

  5. oracle修改数据文件目录

    一.停库修改数据文件目录.文件名 1.当前数据文件目录 SQL> select file_name from dba_data_files; FILE_NAME ---------------- ...

  6. PHP MySQL 快速导入10万条数据

    项目背景 数据来源:所有数据均为外部导入,最大数据量在10w+ 输出数据:导出经过业务处理之后的数据 使用框架:fastadmin 涉及的问题: 1.数据读取 2.数据保存 使用数据:10w+ 解决方 ...

  7. CORS(跨域资源共享)笔记

    0.前言 CORS(Cross-Origin Resource Sharing)是一个用于处理跨域问题的W3C标准,本文将介绍什么是跨域,引起跨域的同源策略,什么是CORS,CORS的工作过程,请求方 ...

  8. 卷积涨点论文 | Asymmetric Convolution ACNet | ICCV | 2019

    文章原创来自作者的微信公众号:[机器学习炼丹术].交流群氛围超好,我希望可以建议一个:当一个人遇到问题的时候,有这样一个平台可以快速讨论并解答,目前已经1群已经满员啦,2群欢迎你的到来哦.加入群唯一的 ...

  9. draggable()拖拽时限制移动区域

    jQuery-UI为我们提供了一个非常便捷的拖拽方法:draggable(),在使用此方法时,我们可能会希望控件只在某一区域中移动,不能被拖出边界,这样的话我们可以使用下面的方法: 调用draggab ...

  10. 解决面具magisk刷入模块卡开机问题

    手机刷入面具模块出现卡开机第二屏,在reccovery模式下,点击高级(advanced)->文件管理(File Manager)->data->adb->modules删掉对 ...