java源码探索系列-01 String

这一系列的源码探索我打算结合一些常见的疑问深入探究一下。答案也许都知道,但是从源码角度来说为什么会这样,我想还是有许多人是模糊状态的。所以一起来带着问题来看看java的设计吧~

String类中包含了许多方法,关于它的构造方法就有十来种。此外还有一些工具方法,比如字符串的比较:equals()contentEquals()compareTo()compareToIgnoreCase()等等,另外还有比如字符串的长度length(),字符串的拼接concat()等等方法....

1. equals()、hashCode()、==的区别?

众所周知,equals() hashCode()在Object类中也有同样的方法:

//equals():就是直接比较对象的内存地址(==)
//另外在看源码的过程中还注意到一个注释:当需要重写equals方法时,必须要重写HashCode方法,因为相等的对象必须具有相等的哈希代码。
/** Note that it is generally necessary to override the {@code hashCode}
 * method whenever this method is overridden, so as to maintain the
 * general contract for the {@code hashCode} method, which states
 * that equal objects must have equal hash codes.
 */
public boolean equals(Object obj) {
    return (this == obj);
}
//hashCode():返回对象的JVM内存地址
public native int hashCode();

下面再来看看String中重写的这两个方法又增加了什么内容吧~

public boolean equals(Object anObject) {
        //第一步,如果引用地址相同就直接返回true
        if (this == anObject) {
            return true;
        }
        //第二步,当地址不同的情况下,当都是Stirng类的实例的时候,然后再去挨个的比较它们的char字符是否相等
        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;
    }
    
private int hash; // Default to 0
// final修饰,因此String的内容也是不变的
private final char value[];// The value is used for character storage.
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;
    }

嗯嗯,代码看完可以直接得出这个问题的结论了。
1. String类中的equals()方法用来比较char内容,而Object类的equals()方法仅仅用来比较对象的引用地址。
2. String类中的hashCode()方法是重新计算出的hashCode数值,而Object类的hashCode()方法返回对象的JVM内存地址。
3. ==是用来比较对象的内存地址的
4. 重写equals()方法必须要重写hashCode()方法。(其实什么引用地址内存地址指的就是一个地址,就是Object类中hashCode()32位JVM地址)

2. 创建String对象有哪些方式?它们的区别是什么?

第一个问题其实是非常清晰的:
1.我们常用到的Stirng s = "xxx"方式;
2.既然它是有构造函数的,我想它肯定也能通过构造函数来创建吧?话不多说直接看代码吧

// 这里只列出几个常用的构造,毕竟它有十几个构造,全列出来文章篇幅有点大...
private final char value[];
private int hash; // Default to 0
public String() {
        this.value = "".value;
    }

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

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

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());
    }

好啦,上面的代码块只是列举出了创建String的第二种方法。还是无法看出这两种创建方式的区别是什么,那下面我们还是用一个例子来比较一下吧~

public static void main(String[] args) {
        String si = new String("ok");
        String ok = "ok";

        System.out.println(si==ok); // false
        System.out.println(ok==("o"+"k")); // true
    }

嗯?第一个为false我知道,但是为什么第二个为true呢?

其实这就引用到JVM堆内存里的常量池的定义了。常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。包括类、方法中的一些常量

那么,这里用new创建的显然是不属于常量池的咯。但是用new的方式如果常量池里面没有的话,它首先也会在常量池里面创建。

是的,其实new就是创建的一个字符串对象。当然,可以使用这个方法intern()返回从字符串池中的字符串对象的引用。

下面再来看一下这个问题

// 在没有创建相同字符串的情况下,下面会创建几个String对象?
String s1 = new String("Sean Blog Site: https://code666.top");
String s2 = new String("Sean Blog Site: https://code666.top");

两个?恭喜你答错了!答案是三个。

第一个是字符串常量池中Sean Blog Site: https://code666.top,另外两个就是堆内存创建的Stirng对象咯。所以应该是三个

还不懂???

其实它的执行顺序是这样的,首先在string池内找,如果找到就不创建string对象,否则创建,这样就有了一个string对象,然后遇到new运算符号了,在内存上创建string对象,并将其返回给s1(s2),又一个对象。

还不懂???

那你的执行顺序应该要这样:搬上你的电脑主机爬上顶楼,然后朝向远处做抛物线运动...

3. String、StringBuilder、StringBuffer的区别?

关于这个经典面试题,我想大多数人都能回答出。

区别:

  1. String创建的字符内容是不可变的,而StirngBuilder和StringBuffer是可变的
  2. StringBuffer是线程安全的,StringBuilder是非线程安全,String也属于线程安全(字符内容是不可变的)
  3. 运行速度(字符拼接数据较多的情况):StringBuilder运行速度最快,StringBuffer运行速度略快,String运行速度最慢
//第一个区别:
//String中char字段
private final char value[];

//StringBuffer
private transient char[] toStringCache;

//StringBuilder:直接使用的是父类AbstractStringBuilder的字段
char[] value;

由上可知,String类的字符内容是不可变的,因为是用的final修饰。而StringBuffer与StringBuilder创建的字符内容都是可变的。transient是用来表示该字段在序列化与反序列化不变,

//第二个区别
//StringBuffer中方法基本都用`synchronized`修饰
@Override
public synchronized int length() {
    return count;
}

@Override
public synchronized int capacity() {
    return value.length;
}

@Override
public synchronized void ensureCapacity(int minimumCapacity) {
    super.ensureCapacity(minimumCapacity);
}

//而StringBuilder中的方法直接用public修饰,没有考虑到并发的情况
@Override
public StringBuilder deleteCharAt(int index) {
    super.deleteCharAt(index);
    return this;
}

@Override
public StringBuilder replace(int start, int end, String str) {
    super.replace(start, end, str);
    return this;
}

由上可知,StringBuffer是属于线程安全,就是因为它的方法都用关键词synchronized来修饰,而StringBuilder则没有。另外,String为什么也是线程安全呢?因为它是不可变的,不可变的肯定也是属于线程安全啊。

第三个区别:

在数据量大的情况下为什么StringBuilder>StringBuffer>String呢?

其实从上面的代码可以得到答案,用synchronized修饰的方法多多少少也会影响到速度了。而String每次都要创建一个新的对象,那肯定也会是最慢的了。

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

推荐阅读更多精彩内容