java基础---String源码

今天刚写的,原地址:https://zhuanlan.zhihu.com/p/31421135

版本是:jdk8

一,类定义

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

1,从中可以看出String不可被继承,因为其是final的。

2,String其实现了java.io.Serializable这个接口说明可以序列化。

为什么实现Serializable了就可以初始化了哪?

这是因为在序列化时,需要调用ObjectOutputStream的writeObject,这个函数对参数进行必要的检查,然后实际调用writeObject0方法,这个方***对对象进行检查,这个对象必须是String对象或数组或Enum类型的对象,或者是实现了Serializable接口的对象,如果都不是,那就抛出NotSerializableException异常。

3,实现了Comparable接口,可以用于比较。

为什么实现了Comparable接口的方法就可以实现比较了吗,这个就比较简单了,因为实现了此接口的集合和数组可以使用Collections.sort 或 Arrays.sort 进行自动排序,这是java API中默认的比较方式,要注意我们自己在实现此接口时要注意在相等时要跟equals一致

那他的CompareTo方法在String中是怎么实现的那?

这个方法先获取两个字符串中最短字符串的长度,然后逐位对比,如果看到有一项字符不相等,那就

return c1 - c2;

这里c1是this的字符,c2是其他字符串的字符,可以看出

v1.compareTo(v2);

若v1就大就返回正数,v1小就返回负数

如果一个字符串呈现包含另一个字符串的关系怎么办哪?

那就返回v1的字符串长度减去v2的字符串长度,相同就是0,从这可以看出字符串长的比较大

3,实现了CharSequence接口

这个是字符序列的接口,这个接口规定了字符序列要实现的方法,如返回序列长度,根据字符位置返回字符,返回子串,规定了必须实现toString方法

这里有两个关于Stream默认方法,等我先去查资料!

算了,日后写一篇单独的文章讲,今天先把String搞定。


二,域

1,CASE_INSENSITIVE_ORDER

public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();

CaseInsensitiveComparator是String的内部类,这个内部类实现了Comparator接口,这个接口也是用于比较,那问题来了Comparator与Comparable有什么区别?

Comparable用于默认的自然顺序排序,可以通过Collections.sort(list) 或Arrays.sort(arr)实现排序,实现这个接口的类可以让自己和别人进行比较,但是当自身不可修改时(如使用了第三方jar包),或者当我们想对一个没有实现Comparable的对象进行排序时,那Comparator就派上用场了,Comparator可以实现任意两个相同类型对象之间比较,

可以使用Collections.sort(list, Comparator) 来实现对list的排序。

Collections有两个重载的sort方法,Arrays同样的道理

参考地址:java Comparable 和Comparator详解及 区别(附代码)

如此看来CASE_INSENSITIVE_ORDER就是一个比较器,在排序时使用这个域:

Collections.sort(List<String>, CASE_INSENSITIVE_ORDER)

这样在排序时就可以不考虑字符的大小写了!

2,serialPersistentFields

private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

这个域是私有的,并且在String类中没有任何地方用到这个字段,还有这个域是长度为0的数组,那它的作用是什么那?

参考:Java深度历险(十)——Java对象序列化与RMI

总起来说,这个域的作用就是防止域被序列化的,但是这个数组长度是0,所以就默认所有的域都会被序列化。

在ObjectOutputStream对象序列对象时会过滤掉ObjectStreamField[]中出现的域。

3,serialVersionUID

这个域就比较简单了,用于表示当前类的版本,版本不同会造成序列化失败。

日后开一篇序列化的文章再详解

4,hash

这个域是用来缓存String的HashCode值的,因为String对象不会变,所有HashCode值也不会变,因此将HashCode值缓存起来可以提高效率,避免重复计算,

5,value

private final char value[];

这个域就是用来存字符串的,可以看出其本质是字符数组,其为final的,所以其必须在构造器被调用时就赋值!

三,方法

1,两个copyValueOf方法

用于将字符数组转换为String,这俩方法只是调用了不同的String构造器

2,两个format方法

public static String format(Locale l, String format, Object... args) {
    return new Formatter(l).format(format, args).toString();
}

将多个对象变成format格式的字符串,Locale是java.util包中的格式包

3,两个join方法

将多个字符串拼接,其内部是使用的java.util.StringJoiner类,本质是使用的StringBuilder

4,9个valueOf方法

用来将各种基本类型和字符数组变成String,布尔类型将变为"true"或"false"

5,16个构造器

用来将byte数组,char数组,String,与StringBuilder类型转换为String

这里需要注意的是在将char数组和StringBuilder类型变为String时,其实使用的是Arrays.copyOf()方法,Arrays.copyOf()方法再调用System.arraycopy()方法。

System.arraycopy()方法是个本地方法,用来申请新的内存,并将字符数组复制进去

之所以要这么做,就是因为字符数组和StringBuilder是可变的,而要保证String是不可变的,就不能简单的复制引用。

还要注意有Charset参数的构造器;

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

这个构造器使用到了StringCoding.decode()方法,将byte数组编码为指定格式

6,codePointAt(int index)

这个方法用来返回指定位置字符的码值

7,compareToIgnoreCase方法

这个方法就是使用的CASE_INSENSITIVE_ORDER域,即比较大小而不考虑字母大小写

8,concat()

连接两个字符串,底层使用的是Arrays.copyOf()

一看就会的方法不赘述了

9,getBytes()系列

实际是通过StringCoding.decode()方法来完成的,规律:涉及byte数组和charset的一般都使用到了StringCoding.decode()

10,intern()方法

public native String intern();

这个方法有趣,参考:通过反编译深入理解Java String及intern

总结一下,①通过字面量生成对象时,会在常量池中查找是否含有该字符串,如有,栈中的引用指向该常量,没有则在常量池中新建一个。new生成的字符串对象会保存在堆中。对于堆中的字符串对象,可以通过 intern() 方法来将字符串添加的常量池中,并返回指向该常量的引用。

②在使用str.intern()方法时,并不会改变str的引用,而是返回一个指向常量池中的引用。若常量池中已存在,则不复制,直接返回常量池中的引用

③字符串字面量的“+”操作,编译阶段直接会合成为一个字符串。这个属于编译器优化

④如果拼接的是字符串对象与字符串字面量,这时候其实是使用的stringBuilder.append()生成的结果,stringBuilder的toString()方法返回字符串对象,字符串对象是存在与堆中的。这个属于语法糖

⑤对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的。

先写到这里吧,明天在挑挑看看还有重要的方法吗。


还需要学习的地方:1,jdk8中的默认方法 2,序列化机制 3,Stream


全部评论
分析得好。。。。楼主厉害
点赞 回复 分享
发布于 2017-11-26 22:54

相关推荐

评论
点赞
35
分享

创作者周榜

更多
牛客网
牛客企业服务