String 源码学习笔记

String 源码学习笔记

迁移到CSDN
对于java程序员来说,String类是在熟悉不过了,但你真的了解它吗?常用构造器有哪些?intern()方法是干什么的?字符串拼接“+”号是如何实现的?我们通过源码一一解答。

本章String源码使用java1.8版本

String以下几个特性决定它是不可变的

​ 1、Stringfinal类,不可继承。

​ 2、字符数组成员变量value使用final修饰,也就是常量,常量一大好处就是线程安全,所以String不需要考虑线程安全问题。

当然如果通过反射还是可以修改value常量值的,这时候会发现如果字符串是在常量池里,那么这个常量池字符串将会被修改成其他值。

​ 3、成员变量value字符数组必须独有,其他程序(不包括String类和反射)不可操作value字符数组

String构造器

默认构造器String()

这个构造器基本上不使用,反正博主是没见过使用它的。

这个很简单,看一下源码就什么都明白了

public String() {
    this.value = new char[0];
}

实际上就是个长度等于0的字符数组,其实就是空字符串"",运行以下案例就什么都明白了

 String empty = "";
 String str = new String();
 System.out.println(empty == (str));//false
 System.out.println(empty == (str).intern());//true 如果不明白是什么后面章节将介绍
 System.out.println(empty.equals(str));//true

字符串构造器String(String original)

使用的也比较少

其实这个构造器就是个克隆的过程,但String是不可变的,所以克隆也是没有多大必要。

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

通过看源码也是一目了然,不在做过多讲解。

字符数组构造器String(char value[])

字符数组构造器有三个重载:

第一种:

   public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

传入一个字符数组,将该数组拷贝一份复制给value,其实这样做就是为了保证String不可变性,介绍不拷贝的话,字符数组中的元素就可以发生变化,如下代码:string成员变量value第一元素就会被修改为'1',这样就破坏了String的不可变性。第三种字符数组构造器就是这样实现的。

char[] value = {'a','b','c'};
String string = new String(value);
value[0] = '1';

第二种:

public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count < 0) {
        throw new StringIndexOutOfBoundsException(count);
    }
    // Note: offset or count might be near -1>>>1.
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

取得传入字符数组的部分元素,在第一种情况下,就是多了对传入的offset count变量判断是否下标越界。

第三种:

/*
    * Package private constructor which shares value array for speed.
    * this constructor is always expected to be called with share==true.
    * a separate constructor is needed because we already have a public
    * String(char[]) constructor that makes a copy of the given char[].
    */
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

可以看到就是将传入字符数组直接赋值给成员变量valueshare变量只是跟第一种构造器做区分使用,并且内部并没有使用该变量,这样做可以节省内存开销运行速率上也有所增加。

该构造器并没有添加访问修饰符,只有同一包内才可以访问,其实就是给jdk内部使用的一种构造器,比如:java.lang.Integer#toHexStringjava.lang.Long#toUnsignedString(long, int)等等。

字节数组构造器String(byte bytes[])

这个在我们平时开发过程中使用的是最多的构造器了,比如:读取文本文件转换成String、网络IO二进制转换成String

有以下几种重载构造器

@Deprecated
public String(byte ascii[], int hibyte, int offset, int count) 
@Deprecated
public String(byte ascii[], int hibyte)

以上两种已经不推荐使用了,在这里就不进行讲解了。

在介绍以下几个构造器之前,说明下变量的含义

bytes:需要转换成字符串的字节数组

offset:字节数组的第一个字节的下标

length:需要转换成字符串的字节长度=

charsetName:编码格式的字符串名称,如:UTF-8

charset:编码格式java.nio.charset.Charset,如:Charset.forName("UTF-8");

public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
}

截取字节数组bytes从下标offset开始截取length长度,字节数组的编码是charsetName,如下样例:

try {
    byte[] bytes = "1234567890".getBytes("UTF-8");
    String str = new  String(bytes, 2, 5, "UTF-8");// 34567
    System.out.println(str);
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
}

其实与String(byte bytes[], int offset, int length, String charsetName)构造器差不多,就是把charsetName改成了charset

public String(byte bytes[], String charsetName)throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
}

外观模式,执行String(byte bytes[], int offset, int length, String charsetName)构造器

public String(byte bytes[], Charset charset){
        this(bytes, 0, bytes.length, charset);
}

外观模式,执行String(byte bytes[], int offset, int length, Charset charset)构造器

public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
}

这个构造器没有指定字节编码,使用的是默认编码Charset.defaultCharset()

public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
}

外观模式,执行String(byte bytes[], int offset, int length)构造器

String(StringBuffer buffer)和public String(StringBuilder builder)

这两个构造器没有什么好说的,只是StringBufferStringBuilder 转换成字符串,但是一般我们都是使用它们俩的toString()方法。

到这里String构造器分析完了,其实我们平时主要使用的就是字符数组构造器字节数组构造器根据不同情况选择不同的参数的构造器。

String.intern()

相信有很多同学已经对该方法有过了解,它是一个本地方法。

public native String intern();

下面是官网对其定义(java8)

public String intern()

A pool of strings, initially empty, is maintained privately by the class String.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java™ Language Specification.

  • Returns:

    a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

这段文字是说明了很多内容

1、字符串常量池,初始是空的,它是由String类私自维护的。

2、当intern方法调用,如果常量池中存在这个字符串(由equals方法判断相等的),则返回常量池中的字符串,否则将这个字符串添加到常量池中,并返回一个对这个对象的引用。

其实就是存在则返回,否则添加并返回。

由此可见,对于任意两个字符串sts.intern() == t.intern()trues.equals(t) 也是 true

3、所有的字符串和字符串值的常数表达式都被插入 ,字符串字面量是在 Java™语言规范的3.10.5. String 字面量中定义的,关于字面

通俗点解释就是,使用双引号""的字符串,全部插入到字符串常量池中,关于字面量请参考字符串字面量小节

举个栗子:

char[] value = {'1','a','2','b'};
String str = "1a2b";//常量池中创建
String s = new String(value);//在堆中创建String对象
String intern = s.intern();//从常量池中获取
System.out.println(str==s);//false
System.out.println(str==intern);//true
System.out.println(s==intern);//false   

为什么要有 intern,需要注意的是什么呢?

字符串常量池就是使用了共享模式,从而提升了效率和减少了内存占用。

要想将堆中创建的的String对象放入常量池中,只需要调用intern方法即可。

注意

只对常用的字符串添加到常量池中,使用次数很少或很长的字符串不要用intern添加常量池,这样会导致常量池中出现很多没有用的和占用内存非常大的字符串从而出现内存泄露, 严重将出现内存溢出

字符串"+"号拼接

平时开发过程中经常会使用"+"号拼接字符串,那么它的实现原理是怎么样的呢?我们通过java反编译看看是如何实现的,下载一个反编译工具cfr_0_132

String +号拼接常用列子

private static void demo1() {
    String a = "123";
    String b = "123" + "456";
    String c = a + "456";

    int intd = 123;
    String stre = "456";
    String f = intd + stre;
    System.out.println(f);
    String h = intd + "456";
    System.out.println(h);
}

通过反编译工具cfr,将上面代码进行反编译后我们看看javac是怎么处理+的,执行反编译命令

java -jar cfr_0_132.jar /src/Projects/self/string/target/classes/com/example/StringPlusSign.class --methodname demo1 --stringbuilder false
private static void demo1() {
    String a = "123";
    String b = "123456";//"123" + "456"
    String c = new StringBuilder().append(a).append("456").toString();//a + "456";
    int intd = 123;
    String stre = "456";
    String f = new StringBuilder().append(intd).append(stre).toString();//intd + stre;
    System.out.println(f);
    String h = new StringBuilder().append(intd).append("456").toString();//intd + "456";
    System.out.println(h);
}

与源代码对比一目了然:

1、+号使用StringBuilder替换了

2、变量b"123"+456"编译成"123456"

字符串字面量

官网描述

A string literal consists of zero or more characters enclosed in double quotes. Characters may be represented by escape sequences (§3.10.6) - one escape sequence for characters in the range U+0000 to U+FFFF, two escape sequences for the UTF-16 surrogate code units of characters in the range U+010000 to U+10FFFF.

大概意思:

一个字符串字面量由双引号括起来的零或多个字符组成。字符可以是转义序列(§3.10.6 ) -一个转义序列字符范围是 U+0000 到 U+FFFF,两个转义序列用于UTF-16代理代码单元格,范围为U + 010000到U + 10FFFF。

下面是字符串字面量示例:

""                    // 空字符串
"\""                  // 一个引号 ” 表示方式
"This is a string"    // 一个包含16个字符的字符串
"This is a " +        // 实际上是一个字符串值常量表达式,
    "two-line string"    // /由两个字符串文字组成

那么java是怎么将字符串字面量加入到字符串常量池的呢?

引用字符串的存储——字符串常量池

当一个.java文件被编译成.class文件时,和所有其他常量一样,每个字符串字面量都通过一种特殊的方式被记录下来。

当一个.class文件被加载时(注意加载发生在初始化之前),JVM在.class文件中寻找字符串字面量。

当找到一个时,JVM会检查是否有相等的字符串在常量池中存放了堆中引用。

如果找不到,就会在堆中创建一个对象,然后将它的引用存放在池中的一个常量表中。

一旦一个字符串对象的引用在常量池中被创建,这个字符串在程序中的所有字面量引用都会被常量池中已经存在的那个引用代替。

字符串字面量的引用实际上是引用了字符串常量池中已存在的字符串对象,这个过程是在JVM加载.class文件是完成的。

接下来通过图文结合的方式说明下字符串引用关系

java代码:

private static void demo2() {
    String a = "abc";
    String b = new String("abc");
    String c = new String("abc").intern();

}

根据上面的的代码,咱们画个图理解下

String内存图.png

变量a是字面量直接引用常量池中的 "abc" ,类加载时

变量b new 了一个String,存放在堆中,运行时

变量c new 了一个String,存放在堆中,掉用intern()方法发现常量池中存在"abc"直接返回其引用,运行时。

总结

本章介绍了String类的各个构造器,那些是常用的,那些是不常用的和如何使用,intern方法是干什么用的及其使用需要注意那些,字符串+号是在编译时处理的,什么是字面量什么时候加载到字符串常量池中。

到这里关于String源码学习就结束了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容