String 基本实现

初学java经常会误认为String是java基本类型,实际上String并非Java基本类型,String本质上是对char数组的封装

以下是String实现相关源码

    /** * 字符实际存储方式 * 由于是fianl,所以String在创建之后,字符数组是无法进行改变* 所以对于String重新赋值,实际上是对重新划分内存空间,创建String*/private final char value[];/*** 构造方法* 进行new操作的时候,实际上是把原字符串的字符数组赋给新字符串的字符数组**/ public String(String original) {this.value = original.value;this.hash = original.hash;}/*** 构造方法* 实质上String 的创建是划分内存,创建字符数组* Arrays.copyOf()实际调用底层System.arraycopy方法,进行内存划分**/ public String(char value[]) {this.value = Arrays.copyOf(value, value.length);}

String 接口分析

String类型所实现的接口

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence

Serializable接口和Comparable接口大家都比较熟悉,一个是序列化接口,一个是比较able类接口,分别对应

//序列化标记号
private static final long serialVersionUID = -6849794470754667710L;public int compareTo(String anotherString) {int len1 = value.length;int len2 = anotherString.value.length;int lim = Math.min(len1, len2);char v1[] = value;char v2[] = anotherString.value;int k = 0;while (k < lim) {char c1 = v1[k];char c2 = v2[k];if (c1 != c2) {return c1 - c2;}k++;}return len1 - len2;
}

CharSequence接口,着这里我们得看一下,为什么常用的String类型不能算是基本类型了

//String的实际内部实现
private final char value[];

CharSequence接口的基本定义节选如下,基本意思是 该接口定义的是 可读 的 字符 序列,提供了 标准 只读对不同类型的 字符序列。

A CharSequence is a readable sequence of char values. This interface provides uniform, read-only access to many different kinds of char sequences.

根据String的定义来看,字符序列实际的体现就是String实际内容是char型数组,只读体现在final上。Final描述的属性是常量属性,对于数组来说,其数组引用无法改变。对于这方面的讨论,之后会有详细的讨论。

//CharSequence提供了如下方法//包内可见
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);//公共
public String toString();

这几种方法也常用到其中subSequence的实际实现就是调用了subString方法

public CharSequence subSequence(int beginIndex, int endIndex) {return this.substring(beginIndex, endIndex);
}

此处的toString方法是将实现该接口的对象提供可转换为 字符序列的方法,虽然命名上与Object的toString方法重复,但从理念上是不同的

//Object toString方法
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

String 加和等于的实现

记得开始学习String时候,对于String的最大疑惑就是 为何String 能实现 +和= 却不是基本类型。

实际上如果我们做个很有意思的实验的话,可以看到如下结果。

//原始代码
System.out.println("Hello" + " world!");//反编译代码
System.out.println("Hello world!");

编译器在进行编译之时,会对不必要的代码进行优化,也就是说一些简单的+运算,编译器已经帮我们做了优化。

//源代码public static void main(String[] args) {String s1 = new String("abcd");char[] chars = {'a', 'b', 'c', 'd'};String s2 = new String(chars);//        System.out.println("s1:" + s1);
//        System.out.println("s2:" + s2);}//反编译Code:0: new           #2                  // class java/lang/String3: dup4: ldc           #3                  // String abcd6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V9: astore_110: ldc           #5                  // String 123412: astore_213: aload_114: astore_315: new           #2                  // class java/lang/String18: dup19: aload_120: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V

从反编译来看,实际上String操作=实际上,是对char数组部分内存进行复制

静态String + 实现

我们知道,java文件在运行之后会被编译为class文件,class文件的基本信息会在jvm方法区中的运行常量池中进行加载,其加载之时就包括字面量的相关信息。实际上反映在jvm之中的字面量,在加载之初就会开辟内存进行存储。

动态String + 实现
JAVA堆主要负责 对象的实例分配,虚拟机栈负责对象引用以及基本数据类型。而在运行时,基本数据类型的内存分配主要集中在虚拟机栈中,而堆中是对于基本数据类型的引用。

加的问题解决了,但final的String value是如何实现等于的呢?
首先我们可以想到 char[] value = “”这种双引号实现的赋值是可以的,但final修饰的char数组是无法重新引用新的内存区域的,也就是说 char数组可以重新赋值数组元素,但不可以变更数组长度。但实际用到的String 长度变更的赋值是如何实现的呢?

实际上

String a = "abc";String a = a + "d"String a = new String("abc" + d");

实际上发生指针改变的Stirng类的引用指正改变,此时的内存划分实在虚拟机栈中的,JVM主要GC实现在堆内存中,因此这种方式一方面浪费内存,另外一方面效率十分差。
在上文中可以看到,底层对于String的赋值工作其实是System.arraycopy进行内存分配,但由于final决定了String不能进行字符数组扩容及改变,所以实际上是重新new了一个String对象。

为了解决String在字符变换时的效率差问题,JDK提供了StringBuffer和StringBuilder

//与String相比,减去了Comparable接口,两个都继承了AbstractStringBuilder
public final class StringBuilderextends AbstractStringBuilderimplements java.io.Serializable, CharSequencepublic final class StringBufferextends AbstractStringBuilderimplements java.io.Serializable, CharSequence//AbstractStringBuilder的定义如下,其增加了Appendable,对应的就是append方法
abstract class AbstractStringBuilder implements Appendable, CharSequence
//实际字符数组存储
char[] value;

StringBuffer和StringBuilder都继承了AbstractStringBuilder ,实际存储方式取消了final修饰符,所以StringBuffer和StringBuilder的字符数组长度可变且可以重新赋值

为了给StringBuilder和StringBuffer提供 +的功能,AbstractStringBuilder通过Appendable接口提供了append方法。

/*** 属性,Char 数组*/
char[] value;/*** 属性,标记数组长度*/
int count;/*** 无参数构造方法,子类 序列化必须提供*/
AbstractStringBuilder() {
}/*** 创建一个指定容量的char数组*/
AbstractStringBuilder(int capacity) {value = new char[capacity];
}//StringBuffer和StringBuilder都是通过调用父类的构造方法进行构造,默认申请16个长度
public StringBuffer() {super(16);
}/*** 自定义长度申请*/
public StringBuffer(int capacity) {super(capacity);
}

StringBuffer和StringBuilder实现capacity变换长度是如何实现的呢?

//AbstractStringBuilder的expandCapacity方法
void expandCapacity(int minimumCapacity) {int newCapacity = value.length * 2 + 2;if (newCapacity - minimumCapacity < 0)newCapacity = minimumCapacity;if (newCapacity < 0) {if (minimumCapacity < 0) // overflowthrow new OutOfMemoryError();newCapacity = Integer.MAX_VALUE;}//这里 Arrays类虽然常用,但copyOf方法没有接触过吧value = Arrays.copyOf(value, newCapacity);
}//Arrays copyOf
public static char[] copyOf(char[] original, int newLength) {char[] copy = new char[newLength];//调用System.arraycopySystem.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;
}/**
注释如下
Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. A subsequence of array components are copied from the source array referenced by src to the destination array referenced by dest. The number of components copied is equal to the length argument. The components at positions srcPos through srcPos+length-1 in the source array are copied into positions destPos through destPos+length-1, respectively, of the destination array.
*/
public static native void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);

String Buffer和StringBuilder主要的区别在于,StringBuffer是线程安全的,StringBuilder非线程安全的。

其主要实现是通过synchronized实现线程安全

//String Buffer实现
public synchronized StringBuffer append(Object obj) {super.append(String.valueOf(obj));return this;
}//String Builder实现
public StringBuilder append(double d) {super.append(d);return this;
}

compareToIgnoreCase、compareTo 和 regionMatches

compareToIgnoreCase这个方法在String 中的实现很有意思,String在源码中生成静态内部类CaseInsensitiveComparator实现忽略大小比较。

仔细查看源码,我们可以看到compare实现了两次比较,之所以使用两次比较,是因为在不同语言环境下(格鲁吉亚语 、希腊语等)单独的toUpperCase或toLowerCase解决不了比较问题。因此使用两次比较。

针对上面的问题,为解决平台平台无关化,考虑String日后扩展,因此将该比较单独抽象做静态内部类。

此处,个人认为对于可访问性最小化原则,compare访问性应该该更为包内可见。

public static final Comparator<String> CASE_INSENSITIVE_ORDER= new CaseInsensitiveComparator();//静态内部类
private static class CaseInsensitiveComparatorimplements Comparator<String>, java.io.Serializable {private static final long serialVersionUID = 8575799808933029326L;public int compare(String s1, String s2) {int n1 = s1.length();int n2 = s2.length();int min = Math.min(n1, n2);for (int i = 0; i < min; i++) {char c1 = s1.charAt(i);char c2 = s2.charAt(i);if (c1 != c2) {//第一次比较c1 = Character.toUpperCase(c1);c2 = Character.toUpperCase(c2);if (c1 != c2) {//第二次比较c1 = Character.toLowerCase(c1);c2 = Character.toLowerCase(c2);if (c1 != c2) {// No overflow because of numeric promotionreturn c1 - c2;}}}}return n1 - n2;}
}public int compareToIgnoreCase(String str) {return CASE_INSENSITIVE_ORDER.compare(this, str);
}

CaseInsensitiveComparator实现Comparator和Serializable接口。Comparator这个接口有没有看到有些熟悉,String里边扩展了接口comparable接口。 jdk中对于命名使用是比较规范的,这点值得借鉴。

public interface Comparator<T> {int compare(T o1, T o2);boolean equals(Object obj);
}public interface Comparable<T> {public int compareTo(T o);
}

对于comparator,我们可以看到compare方法的实现是趋于静态工厂方法的,而Comparable方法是属于类型能力接口,所比较的是对象主体。

两次比较的策略所能解决平台无关化问题,但随之带来的问题是性能的牺牲。String中提供了regionMatches,regionMatches所返回的是boolean型值,所以只能匹配是否相等,并不能比较大小,但根据源码来看,该方法提供了单次比较的实现。

public boolean regionMatches(boolean ignoreCase, int toffset,String other, int ooffset, int len) {char ta[] = value;int to = toffset;char pa[] = other.value;int po = ooffset;// Note: toffset, ooffset, or len might be near -1>>>1.if ((ooffset < 0) || (toffset < 0)|| (toffset > (long)value.length - len)|| (ooffset > (long)other.value.length - len)) {return false;}while (len-- > 0) {char c1 = ta[to++];char c2 = pa[po++];if (c1 == c2) {continue;}if (ignoreCase) {// If characters don't match but case may be ignored,// try converting both characters to uppercase.// If the results match, then the comparison scan should// continue.char u1 = Character.toUpperCase(c1);char u2 = Character.toUpperCase(c2);if (u1 == u2) {continue;}// Unfortunately, conversion to uppercase does not work properly// for the Georgian alphabet, which has strange rules about case// conversion.  So we need to make one last check before// exiting.if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {continue;}}return false;}return true;
}

String 常用方法介绍

返回指定位置的字符,从0开始记

public char charAt(int index) 

返回指定位置的字符的unicode编码,从0开始记

public int codePointAt(int index)

返回指定位置前一位的字符的unicode编码,从0开始记
其实也可以理解为指定位置字符unicode编码,从1开始记

public int codePointBefore(int index)

查询范围 endIndex-beginIndex
endIndex范围取 0~ length
endIndex范围取 0~ length + 1

public int codePointCount(int beginIndex, int endIndex)

比较大小
从首位开始比较,若不同输出 s - anotherString 值
相同输出0

public int compareTo(String anotherString)

比较大小忽略大小写

public int compareToIgnoreCase(String str)

合并字符串
相当于append

public String concat(String str)

替换字符串

public String replace(char oldChar, char newChar)

正则表达式匹配

public boolean matches(String regex)

判断是否包含
CharSequence接口String及其配套类(StringBuffer、StringBuilfer)实现

public boolean contains(CharSequence s)

替换 正则表达式匹配到的第一个 字符串

public String replaceFirst(String regex, String replacement)

替换 正则表达式匹配到的所有字符串

public String replaceAll(String regex, String replacement)

替换字符串中 target 为 replacement

public String replace(CharSequence target, CharSequence replacement)

转载于:https://www.cnblogs.com/cunchen/p/9464150.html

【JAVA源码分析——Java.lang】String源码分析相关推荐

  1. 程序兵法:Java String 源码的排序算法(一)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 <程序兵法:Java Str ...

  2. java自带的字节码技术_读懂字节码-还原JAVA源码

    已知有两个类: public class Father extends GrandFather { public String name = "father"; public vo ...

  3. spring源码分析03-spring依赖注入源码解析

    依赖注入流程图: 1. Spring中有几种依赖注入的方式? 1.1手动注入 在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属性指定了值. 下面这种底层是通过set方法进行注入. &l ...

  4. java字符串构造函数的应用_构造函数中的参数0需要找不到类型为'java.lang.String'的bean...

    我正在使用Spring Boot 2.X应用程序进行Spring Batch,实际上我已经从git中检出了它的现有代码.在运行该应用程序时,由于以下错误而导致失败仅对我自己,而相同的代码对其他人也有效 ...

  5. HotSpot源码(一):Docker与虚拟机的区别,class字节码解析,linux内核源码下载地址,Yacc与Lex快速入门

    Docker是虚拟机吗? Docker是用来隔离的,使用的是隔离的namespace,使用OS提供的接口进行应用程序之间的资源隔离,不是虚拟机.再加上它自己特殊的文件系统,一层一层叠加.他只不过是一个 ...

  6. 【JVM】通过javap命令分析Java汇编指令

    文章目录 javap命令简述 javap测试及内容详解 例子1 例子2 总结 转载说明 javap命令简述 javap是jdk自带的反解析工具.它的作用就是根据class字节码文件,反解析出当前类对应 ...

  7. 通过javap命令分析java汇编指令

    一.javap命令简述 javap是jdk自带的反解析工具.它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令).本地变量表.异常表和代码行偏移量映射表.常量池等等信息. ...

  8. JVM插码之五:Java agent+ASM实战--监控所有方法执行时间

    本文建立在对instrumentation和agent有初步的了解的前提下阅读,关于这2个类的讲解在其它文章中. 这是一个maven项目,pom中需要的配置,lib中有asm的jar包 pom.xml ...

  9. 从字节码看java中 this 的隐式传参

    从字节码看java中 this 隐式传参具体体现(和python中的self如出一辙,但是比python中藏得更深),也发现了 static 与 非 static 方法的区别所在! static与非s ...

  10. Java 8 动态类型语言Lambda表达式实现原理分析

    Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标. import java.util.function.Co ...

最新文章

  1. LeetCode (2) - swift 实现数组 加一
  2. Java局部变量一定要赋初值
  3. 大数据表转移hdfs后查询处理
  4. GooFlow有后门代码
  5. iOS捷径(Workflow 2.0)拓展
  6. 鸡兔同笼编程语言c,鸡兔同笼用c语言编程
  7. 数字信号处理前瞻(note1):奈奎斯特与折叠频率
  8. 新手小白做短视频自媒体,入门级教程分享,抓紧收藏
  9. 盘点——iOS应用开发常用工具
  10. 第三届同花顺算法大赛 | 2022 | AI算法
  11. 深度学习-吴恩达第一课第二周课程作业
  12. 拯救剧荒!程序员最爱看的美剧TOP5!
  13. OPENGLES 绘制纹理带黑圈pre-multiplying
  14. XSSFWorkbook下载excel表格
  15. 类似visio的拓扑图节点连线控件免费下载
  16. Ae效果控件快速参考:模糊和锐化
  17. YDOOK:ESP8266EX 管脚布局与定义
  18. 2015英国女王殿下的圣诞致辞
  19. ArcGIS地面粗糙度提取
  20. 网页自动提交Form表单的方法

热门文章

  1. android shape 按钮背景_Android UI:XML文件配置按钮等背景方案
  2. 的pro文件添加opencv配置_VS2013+OpenCV3.1.0配置方法
  3. 仿真软件测试基尔霍夫定律,基尔霍夫定律实验报告范文
  4. java修改cookie的值_Java管理Cookie增删改查操作。
  5. 小程序数据框有重影_关于拖动滚动条编辑框数据重影的问题
  6. java线程下载文件_使用多线程在Java下载文件
  7. linux和python那个好学_Python和C#哪个好学?老男孩python
  8. @scheduled注解配置时间_「玩转SpringBoot」用好条件相关注解,开启自动配置之门...
  9. html5中加入视频格式,HTML5音视频格式video和audio
  10. CSS去掉TextBox边框