深入理解Java String源码,用法,原理,存储方式

前言:

《朱子语类》卷九《论知行》篇中说: “不可去名上理会。须求其所以然。”

作为一名Java新晋程序员,夯实基础是职业生涯实现长期可持续发展不可或缺的日常任务。随着高速的技术更新迭代,新兴的技术架构使得大部分编程学者的追慕,但却遗失了根本的原理,从而使我们留在这个方法我会用,但不知到它是怎么实现的层面。好像我们小时候背课文、背古诗,这篇课文我会背但不知道是什么意思就无法懂得诗篇美句及其作者背后的情感,就直到长大成人后的一些经历才真正的唤醒了我们对那篇诗句的共鸣,从而了解了诗篇的美好以及它告诉我们的真谛。


本文作者抱以学习的心态去学习并尝试深入理解 java.lang.String 类的原理。在学习之初,作者进行了一系列研究,搜索,收集String有关的知识点并对本文中所讨论的String类从以下几方面进行分析;知其然,并求其所以然。

  • Java为什么会有String? 及其作用是什么?
    String类是Java语言中最常用的一个类,用于描述字符串事物。举例来说,在互联网中大家所看到的一般信息类文字一大部分就是以字符串的形式出现的,比如一些网站中注册所填写的信息内容,网购中填写的收货地址等等;在Java中,String基本上是表示char值序列的对象。
public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence {  
    /** The value is used for character storage. */  
    private final char value[]; 
  • String为什么不可变(immutable)?
    首先String类是final修饰的,这就说明String不可被继承;而String储存在char字符数组value中,value也是被final关键字修饰,这就说明value创建以后地址也是不可变的;还有一个也比较重要,那就是private修饰的value,并且value没有提供set、get方法,这就保证外部无法直接去操作value数组,这一点也非常重要。

    除此以外,String内部的各个逻辑方法都没有对value数组进行直接的修改,而是拷贝到新的字符数组中,这一点是保证String不可变的最重要的因素。因为value只是一个指向堆内存的指针,value不可变,但是value所指向的堆内存中的对象的内容是可以变的,如果不是String内部一系列底层实现,仅仅依靠private和final是根本没有办法保证String不可变的。


    /* * @param  original
     *         A {@code String}
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
  • String 构造方法源代码
    这说明从用String去new String构造新的对象是,确实会产生新的对象,而且新的value和hash值都和原String对象一致。

String str1 = "Hello Qiyi";
String str2 = new String("Hello Qiyi");
  • 创建String对象
    以上两种方式都可以打印出“Hello Qiyi” 这个字符串,str1字面量赋值会自动隐式调用intern()方法直接将字符串存储在常量池中(后面会讨论版本差异问题);而str2通过String类构造函数和new关键字创建一个新的String实例对象在堆内存中。

    public boolean equals(Object anObject) {
        if (this == anObject) { //对象引用地址的比较
            return true;
        }
        if (anObject instanceof String) { //如果是String类
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {//判断长度是否相等
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//再循环判断每一个字符是否相等
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
  • equals() 方法源码分析
    判断条件:
    若当前对象和比较的对象是同一个对象,即return true。也就是Object中的equals方法。
    若当前传入的对象是String类型,则比较两个字符串的长度,即value.length的长度。
    若长度不相同,则return false
    若长度相同,则按照数组value中的每一位进行比较,不同,则返回false。若每一位都相同,则返回true。
    若当前传入的对象不是String类型,则直接返回false。
    注:== 比较的是对象的引用地址,equals()方法比较的是字符串值。

 public native String intern();
  • intern() 方法源代码
    它的大体实现结构就是:JAVA 使用 jni 调用c++实现的StringTable的intern方法, StringTable的intern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。一个Native Method就是一个java调用非java代码的接口。尽管在输出中调用intern方法并没有什么效果,但是实际上后台这个方法会做一系列的动作和操作。在调用"Hello Qiyi".intern()方法的时候会返回"Hello Qiyi",但是这个方法会首先检查字符串池中是否有"Hello Qiyi"这个字符串,如果存在则返回这个字符串的引用,否则就将这个字符串添加到字符串池中,然后会返回这个字符串的引用。这样其实是运用了享元设计模式思想避免重复创建新对象,就会大大减少对象的创建,降低系统的内存,提高性能。
    在 JDK6 下,intern()会把首次遇到的字符串实例复制到永久代中,返回的也是这个永久代中字符串实例的引用;而在JDK1.7开始,intern()方法不在复制字符串实例,String 的 intern 方法首先将尝试在常量池中查找该对象的引用,如果找到则直接返回该对象在常量池中的引用地址,如果常量池中没有,会将此字符串放入常量池后再返回。
    String str3 = new String("Hello") + new String(" Qiyi");
    str3.intern();
    String str4 = "Hello Qiyi";
    System.out.println(str3 == str4);//jdk6: false 、jdk7: true
  • 不同版本的JDK对intern方法有着些许不同的影响;
    在JDK6中,常量池是位于非堆内存的永生代(Perm)区域
    str3 通过new关键字被创建在 Java Heap(堆)中,str3.intern()首次将"Hello Qiyi" 放入String常量池生成一个对象,所以"Hello Qiyi" 会在常量池中,而str3本体地址还在堆内存中;
    str4 会直接去常量池中寻找,如果有就引用如果没有就创建。
    所以,str3的引用地址在堆内存,str4的引用地址在非堆内存的常量池;两个不同区域的地址肯定不相同返回结果为false。
    在JDK7中,常量池是位于堆内存的永生代(Perm)区域
    str3 还是被创建在堆中,不过与别在于调用intern()方法后不用再在常量池生成一个对象而是直接存储堆中的引用;
    str4 也是直接存储字符串到常量池,所以也在堆中;

  • String常用方法
    trim()
    作用是去掉字符串的前后空格,在字符串传值比较中比较常用
    public String trim() {
        int len = value.length;//字符串的长度
        int st = 0;
        char[] val = value;    /* 赋值给新的char数组,避免getter操作 */
        //当     小于长度 并且 从头部位置小于等于空格(字符比较按照ASCII表)
        while ((st < len) && (val[st] <= ' ')) {//找到字符串前段没有空格的位置
            st++;
        }
        //当     小于长度 并且 从尾部位置小于等于空格
        while ((st < len) && (val[len - 1] <= ' ')) {
            len--;
        }
        //没有出现空格返回本身 反之返回头尾部位置的字符串
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

replace()
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。

                         /*被替换字符*/ /*替换字符*/
    public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;//取得整个字符串的长度。 
            int i = -1;
            char[] val = value; /* avoid getfield opcode *///复制一份字符串数组。

            while (++i < len) {
                if (val[i] == oldChar) {//找到被替换字符的位置
                    break;
                }
            }
            if (i < len) {//如果这个位置小于字符串的长度。
                char buf[] = new char[len];//申请一个和原来字符数组一样大小的buf数组
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];//把替换字符循环放入
                }
                while (i < len) {
                    char c = val[i];
                    //在剩余的字符中,每遇到一个旧字符就用新的字符替换
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);//将构造的新的字符串对象返回。
            }
        }
        return this;
    }

本文将持续修订,如有错误欢迎大家指正!

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