【Java 学习笔记】从源码了解字符串

之前写过一篇关于 String 类、StringBuilder 和 StringBuffer 的基本介绍,今天从 String 类的部分源码来看 String 类(本文基于 JDK 1.8)

  1. String 类的实例化
  2. 常用方法
  3. String 类的不可变
  4. 总结

1. String 类的构造器

先看看 String 类中定义的一个常量和一个变量:

    /** The value is used for character storage. */
    //存储字符
    private final char value[];

    /** Cache the hash code for the string */
    //字符串的哈希值
    private int hash; // Default to 0

翻了以下源码,发现从 100 - 600 行左右都是写的构造器,数不胜数,这里就选几个常见的吧:

先来说说连开发者都觉得没用的构造器吧:

    //初始化一个 String 对象,它代表了一个空的字符序列
    //我们平常直接就 String str = "";就好了
    public String() {
        this.value = "".value;
    }

    //初始化一个和参数一模一样的对象,除非你是要保留副本,不然没什么用处
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

然后是根据字符数组来进行初始化的构造器:

    /**
     * 用一个字符数组当前所储存的字符来初始化字符串,
     * 后续对数组的修改将不会对字符串有影响,感觉在做
     * String 类的题目时,较常用到这个方法
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }


    //截取字符数组中的某一部分来初始化
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // 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);
    }

最后是根据 StringBuffer 和 StringBuilder 来进行初始化的构造器:

    //StringBuffer 是线程安全的,所以需要加锁
    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }

    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

这里还有个很有意思的包私有构造器(看不懂):

    /*
    * 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;
    }

后来去 Google 了一下有关字符串共享的相关内容,还是不太明白,可能是因为常量池的原因,在常量池中存放着字符串的引用,就会导致字符串被共享,这个构造器可能就是共享时的构造吧(待确定),文末给出写文时查询这一部分内容的链接,这一部分内容等下一篇讲述常量池时一起说说

2. 常用方法

1. 基本的

常用的 length(), charAt(), isEmpty() 等等都是根据根据字符数组来做操作的,就看其中一个吧:

    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

2. hashCode()

这个方法是重写了 Object 类的 hashCode:

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

在 哈希值等于 0 且字符串不为空时,才计算哈希值,否则就返回 0,计算方法就是 for 循环里的,举个简单的例子算一下:

字符串 ab 的哈希值就是 97 * 32 + 98 得出的

通过对字符串的哈希值的比较,能够得出两个字符串的值是否相同,但是却不能用来比较两个对象的引用

3. join()

这个方法以前还都没听过,这次刚好看见了,就说一下用法:

    //第一个参数时分隔符,第二个是可变参数
    public static String join(CharSequence delimiter, CharSequence... elements) {
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Number of elements not likely worth Arrays.stream overhead.
        StringJoiner joiner = new StringJoiner(delimiter);
        for (CharSequence cs: elements) {
            joiner.add(cs);
        }
        return joiner.toString();
    }

这里引出另一个类:StringJoiner,这里不介绍,如果只有一个字符串,就不会添加分隔符,若有多个,则在每两个拼接的字符串中添加分隔符:

如果要拼接多个字符,并且要添加分隔符,用 join() 会方便很多

4. equals()

== 和 equals() 的区别相信都应该知道了,前者比较引用(引用相同,值自然就相同),后者只比较值:

    public boolean equals(Object anObject) {
        //如果引用相同,就直接返回 true
        if (this == anObject) {
            return true;
        }
        //逐个比较字符
        if (anObject instanceof 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;
    }

3. String 类的不可变

一直都在说 String 类是不可变的,究竟是怎么实现呢?

首先,String 类本身被声明为 final:public final class String,不能被继承,所以不可能重写 String 类的方法(只读),整个 String 类的核心:private final char value[] 也被声明为 final

其次,在源码中的返回值为 String 类型的方法,如果方法中修改了字符串的结构,在最后都是 return new String(xxx) 这样子,如果没有修改字符串结构,就返回这个字符串,也就是所说的:任何修改字符串结构的操作都会产生一个新的 String 对象

4. 总结

其实对于 String 类,最重要的点就是不可变,至于其它的一些操作,感兴趣的可以去翻一下源码(3100 行,不多),注释写的非常清楚,很多操作其实伴随注释看一遍就可理解),就没有写在这篇文章里

最近开始着手准备明年的春招了,所以刚好写博客复习一下 Java 的一些基础知识,下一篇会介绍一下常量池

参考资源:

很早之前的一篇文章,JDK 版本还没到 1.8,所以看一看就好了http://chunlong.github.io/blog/2013/05/23/something-about-string/

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

推荐阅读更多精彩内容