细读源码之Java String (一)

简介: 细读源码之Java String (一)

Java中的String类绝对是使用最广泛的类,本文就带着大家细读一下JDK8下的String源码,主要包含以下几个方面的内容:

一.String不可变特性

二.String核心字段分析

三.String常见方法分析

四.String在Jdk1.6和1.8版本的差异

image.gif编辑

一.String的不可变特性

1.什么是不可变对象?

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。

2.String设计成不可变的好处

A.只有当字符串是不可变的,字符串常量池才有可能实现,才能把字面值相同的字符串都指向常量池中的同一地址空间,达到节约堆内存的目的;

B.字符串是不可变的,必然是线程安全的,这样同一个字符串实例就可以被多个线程安全地共享,而且不需要任何同步操作;

C.字符串是不可变的,才能缓存hash的值,避免重复计算,使得String作为HashMap的Key,具有很好的性能。

3.String类如何实现不可变

1.String类定义使用final修饰,不能被继承,这样String的所有方法就不能被重写;

2.存储字符串数据的字段声明为private final char value[],使用private final修饰,value自身的值初始化后不能被修改,也不能被外部访问;

3.String的构造函数,通过拷贝等方式,保证String内部value字段不能被外部访问;

4.任何需要改变String内容的方法,都创建了一个新的String对象,而原对象保持不变,如substring,trim,toUpperCase等;

5.获取value的方法,都返回了value的一个拷贝,如toCharArray。

4.String类不可变性破坏

虽然value字段被private修饰,但是还是能通过反射的方式获取并进行修改,代码如下:

private static void main() throws IllegalAccessException, NoSuchFieldException {    String a = "1";    Field valueField = String.class.getDeclaredField("value");    System.out.println(a);    valueField.setAccessible(true);    char[] value = (char[]) valueField.get(a);    value[0] = '0';    System.out.println(a);}

通过上面代码,就把变量a的值由1变成0。

二.String的核心字段

1.value

value的定义为private final char value[],存储字符串中的每一个字符,并保证不被外部访问。

2.hash

hash的定义为private int hash,存储的是字符串的hashCode的值,与hash关联的有两个问题:

A.为什么要存储hash值?

因为字符串的hashCode方法计算非常耗时,时间复杂度是O(N),如果每一次用到,都需要计算,严重影响性能,所以有必要缓存hash的值;

2.为什么hash字段没有使用final修饰?

如果使用final修饰,hash的值必须在对象初始化完成之前完成赋值,而hash的值的计算又很耗时,对于那些不需要使用hash的字符串,计算好了又不使用,会造成CPU的浪费。所以这里就采用延迟计算的策略,默认值为0,需要使用的时候再计算,要达到这样目的hash字段就不能被final修饰。

三.String常见方法分析

1.String加法

Java中不容许操作符重载,所以String的加法是通过语法糖(编译手段)来实现的,具体分为以下两种情况:

A. 执行相加操作时,结果值在编译期确定,就直接使用相加后的结果进行替换,这样就消除加法操作。举例说明:

例1:

public static void main(String[] args) {    String value = "a" + "b";    System.out.println(value);}

因为字符串"a"和"b"都是字面常量,value的值在编译的时候就已经确定,值为"ab",所以直接把"ab"赋值给了value。我们反编译上面代码生成的class文件,得到的等价代码如下:

public static void main(String[] args) {    String value = "ab";    System.out.println(value);}

 

例2:

public static void main(String[] args) {    final String a = "a";    final String b = "b";    String value = a + b;    System.out.println(value);}

因为变量a和b都使用final修饰,值为字面常量,编译期确定,所有在编译期确定a + b的值就是就是"ab",就直接把"ab"赋值给了value,我们反编译上面代码生成的class文件,等价代码如下:

public static void main(String[] args) {    String a = "a";    String b = "b";    String value = "ab";    System.out.println(value);}

B. 执行相加操作时,结果值在编译期不能确定,则使用StringBuilder进行拼接,举例说明:

例3:

public static void main(String[] args) {    String a = "a";    String b = "b";    String c = "c";    String value = a + b + c;    System.out.println(value);}

因为a,b,c三个变量,没有final修饰,值可能在运行的过程中被修改,所以value的值就是编译期不定,编译的时候转换为使用StringBuilder进行拼接,反编译后等价代码如下:

public static void main(String[] args) {    String a = "a";    String b = "b";    String c = "c";    String value = new StringBuilder().append(a).append(b).append(c).toString();    System.out.println(value);}

例4:

public static void main(String[] args) {    final String a = String.valueOf(System.currentTimeMillis());    final String b = String.valueOf(System.currentTimeMillis());    String value = a + b;    System.out.println(value);}

虽然变量a和b使用了final修饰,但是a和b的值在编译期不确定,所以value的值也是不定的,最后只能使用StringBuilder进行拼接,反编译后等价代码如下:

public static void main6(String[] args) {    final String a = String.valueOf(System.currentTimeMillis());    final String b = String.valueOf(System.currentTimeMillis());    String value = new StringBuilder().append(a).append(b).toString();    System.out.println(value);}

说明一下,String加法在编译的时候,究竟是采用用结果值直接替换还是使用StringBuilder进行拼接,跟进行相加的变量是否使用final修饰没有直接关系,唯一的判断依据就是编译期加法结果的值是否确定,确定就替换,不确定就拼接。

例5:

public static void main(String[] args) {    String a = "";    for (int i = 0; i < 26; i++) {        a += (char) ('A' + i);    }    System.out.println(a);}

这个例子用来说明低效使用String加法运算,此过程会频繁的创建String和StringBuilder对象,所以一定避免在循环中使用加号对字符串进行拼接,上面代码反编译后等价代码如下:

public static void main(String[] args) {    String a = "";    for (int i = 0; i < 26; i++) {        a = new StringBuilder().append(a).append((char) ('A' + i)).toString();    }    System.out.println(a);}

效果更高的写法弃用加法,直接使用StringBuilder进行字符串拼接,只创建一个StringBuilder对象,只调用一次builder.toString()方法,代码如下:

public static void main(String[] args) {    StringBuilder builder = new StringBuilder();    for (int i = 0; i < 26; i++) {        builder.append((char) ('A' + i));    }    String value = builder.toString();    System.out.println(value);}
相关文章
|
2月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
84 7
|
3天前
|
JavaScript 安全 Java
智慧产科一体化管理平台源码,基于Java,Vue,ElementUI技术开发,二开快捷
智慧产科一体化管理平台覆盖从备孕到产后42天的全流程管理,构建科室协同、医患沟通及智能设备互联平台。通过移动端扫码建卡、自助报道、智能采集数据等手段优化就诊流程,提升孕妇就诊体验,并实现高危孕产妇五色管理和孕妇学校三位一体化管理,全面提升妇幼健康宣教质量。
28 12
|
7天前
|
人工智能 监控 安全
Java智慧工地(源码):数字化管理提升施工安全与质量
随着科技的发展,智慧工地已成为建筑行业转型升级的重要手段。依托智能感知设备和云物互联技术,智慧工地为工程管理带来了革命性的变革,实现了项目管理的简单化、远程化和智能化。
28 4
|
25天前
|
JavaScript Java 测试技术
基于Java+SpringBoot+Vue实现的车辆充电桩系统设计与实现(系统源码+文档+部署讲解等)
面向大学生毕业选题、开题、任务书、程序设计开发、论文辅导提供一站式服务。主要服务:程序设计开发、代码修改、成品部署、支持定制、论文辅导,助力毕设!
56 6
|
1月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
存储 JavaScript Java
Java 中的 String Pool 简介
本文介绍了 Java 中 String 对象及其存储机制 String Pool 的基本概念,包括字符串引用、构造方法中的内存分配、字符串文字与对象的区别、手工引用、垃圾清理、性能优化,以及 Java 9 中的压缩字符串特性。文章详细解析了 String 对象的初始化、内存使用及优化方法,帮助开发者更好地理解和使用 Java 中的字符串。
Java 中的 String Pool 简介
|
2月前
|
缓存 安全 Java
java 为什么 String 在 java 中是不可变的?
本文探讨了Java中String为何设计为不可变类型,从字符串池的高效利用、哈希码缓存、支持其他对象的安全使用、增强安全性以及线程安全等方面阐述了不可变性的优势。文中还通过具体代码示例解释了这些优点的实际应用。
java 为什么 String 在 java 中是不可变的?
|
2月前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
150 13
|
3月前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
76 12
|
2月前
|
存储 Java
Java 11 的String是如何优化存储的?
本文介绍了Java中字符串存储优化的原理和实现。通过判断字符串是否全为拉丁字符,使用`byte`代替`char`存储,以节省空间。具体实现涉及`compress`和`toBytes`方法,前者用于尝试压缩字符串,后者则按常规方式存储。代码示例展示了如何根据配置决定使用哪种存储方式。