String 类轮廓

String类轮廓

内存轮廓

注意这张图,一张很简单的图,我试着在上边添加其他元素,但是最后还是保留成这张简易的图

String在虚拟机中的存储

首先我们要明确一个概念:栈、堆都是在运行时的概念,字面池自然也是。注意以下代码,分析其在内存中的存储情况

    String s01=null;
    String s02="";
    String s03="aaa";
    String s04=new String("");
    char[] chs= {'a','b','c'};
    String s05=new String(chs);
    String s06="aaa";
    String s07=new String("ddd");

这其中涉及到三个地方:方法栈、堆、字面池(在堆中),栈中会存储:

  • s01
  • s02
  • ...
  • s0x

字面池中存储

  • ""
  • "aaa"
  • "ddd"

堆中会存储字符数组3个字符数组的引用:

  1. 指向常量池中的""#其字符数组表示为{}
  2. 指向常量池中的"ddd"#其字符数组表示为{}
  3. 指向堆中(非常量池)的{'a','b','c'};

这里面的细节非常有意思,如过此时不明白也没关系,先看后面的,再到回来看着这几个论断。

术语#引用变量

在正式的进入之前先自定义一个属于:引用变量,嗯或许在此处引用变量与引用是等同的,但是我不喜欢引用这个词,因此创建一个引用变量的术语。

引用变量被定义为:是一个与基本数据类型几乎等价的普通变量,用于指向一个类实例。引用变量具有以下特性:

  1. 同基本数据类型一样被存放在方法栈中
  2. 引用变量指向一个类实例或者null,本质是变量的值可以保存一个类实例在jvm中的地址
  3. 如代码“String s01=null;”中的s01就是一个引用变量

(引用也许就是指此,我见过太多的暗喻,这些暗喻使得引用这个词有非常大的歧义性,因而不愿使用引用这个词)

String 属性

value

value是一个引用变量,用于指向该String的实际数据部分,应当注意在底层是不存在类String这样的概念的,String以及其他类的本质是对数据和对数据的操作进行封装,在jdk源码中,value被这样定义:

private final char value[];

final关键字修饰value的结果是value一旦被初始化后其指向不可改变,实际上任何一个String类的实例仅在其构造函数处对value进行依次赋值,如:

public String() {
    this.value = "".value;
}

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

注意这3个构造函数在定义,注意第一个构造函数,里面会发生一些有趣的事情,第一个构造函数创建String实例时,非常注意这样一点,空字符串""一老早就被放到字面池内了,之后构造函数会做这样一件是,把字面池中空字符串""的地址丢该value,之后便完成创建。

第二个构造函数与第一个的过程基本一致,只是还把hash值也作了初始化,关于hash后边会做介绍。

第三个构造函数就不一养了,我们去看Array.copyof源码,该方法返回一个全新的数组引用变量,并赋值给value

public static byte[] copyOf(byte[] original, int newLength) {
    byte[] copy = new byte[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

这三个构造函数(包括其他的构造函数)都完成了一件事:将value与一个字符数组实例绑定,注意该绑定不可改变。这个时候,我们可以通过value变量去读和写之绑定的字符数组内部内容,但是检查String所有的代码,没有发现任何修改该这个数组的代码存在!仅有读取,比如方法subString(int,int)返回null或者一个新创建的数组(类似的方法的源码后边会展示),这样的结果使得字面池中的所有值好像从创建到死亡,都不会被修改,因此许多人很简单的把字面池类比与常量去认识,这既对又不太对。

hash

暂时留白,等后边填充

String 方法

charAt

charAt这个方法很简单,有趣的是我们返回去看value属性,value是一个字符串数组,java中数组是一个特殊的类,该类有一个实例属性:count。

在设计上,字符串类最简单的实现方法是在类中直接包含一个字符串,但是这样操作的话远远不及源码中的优雅和快速。

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

length & isEmpty

length方法就更加简单粗暴了,但是却又很自然。

public int length() {
    return value.length;
}
public boolean isEmpty() {
    return value.length == 0;
}

getChars

前文中有说道,整个String代码没有任何一处改写了value指向的空间,仅仅是读取,这在下面的代码中体现的非常明显,当然我感觉另一种原型会更自然一些 “char [] getchars()”

void getChars(char dst[], int dstBegin) {
    System.arraycopy(value, 0, dst, dstBegin, value.length);
}

实用方法群

  1. String trim()

  2. boolean startsWith(String)

  3. boolean endsWith(String)

  4. String []split(String regex)

  5. String replace(char oldChar,char newChar)

  6. boolean matchs(String reg)

  7. int indexOf(int ch)

  8. String format(String format,Object...args)

  9. String format(Locale l,String format,Object...args)

    格式或字符串,有需要的时候参阅java.util.Formatter

    System.out.println(String.format("姓名:%s\n年龄:%d","Chenlin",18));
    
  10. String concat(String)

  11. boolean contains(CharSequence)

  12. boolean contentEquals(Charsequence)

  13. boolean contentEquals(StringBuffer)

xx方法群

此方法群不太明白,不清楚下三个方法究竟对数据做了怎样的转换

  1. byte [] getBytes()
  2. byte [] getBytes(Charset)
  3. void getChars(int srcStaer,int srcEnd,char[] dst,int dstBegin)

valueOf方法群

父接口

Srerializable

序列化接口,此接口不通过任何方法,仅作为一种语义标记,实现该接口的类可以序列化和反序列化,否则不可

Comparable

Comparable提供compareTo方法,compareTo方法用于将对象与制定对象相比较,此外还有一些其他的规定,暂时不能理解这些规定是为啥,String中Compare方法的实现如下代码

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

CharSequence

CharSequence接口同以下方法

  • char charAt(index)
  • default IntStream chars()
  • defualt IntStream codePoints()
  • int length()
  • CharSequence subSequence(int start, int end)
  • String toString()

不是很明白此接口有什么价值,源码实现如下:

public String toString() {
    return this;
}

    public CharSequence subSequence(int beginIndex, int endIndex) {
        return this.substring(beginIndex, endIndex);
    }

chars和codePoints使用接口默认的实现。

debug

(中途感受,留存)

在hash处遇上了一个问题,我清楚hash值在String类中的价值和在。翻看了多篇博客没有找到答案

这篇博文可能还需要一整天才能理出来,主要的点是这样几个

  • 和char[]、 byte[]互转,这里涉及到Java中处理字符编码的问题
  • hashCode,虽然可能不太实用,但是就想知道这玩意究竟价值在何
  • equals方法
  • valueOf方法群

把String源码看完了,只是书写起来很慢,String类的设计,嗯,远比想象中来的简单粗暴,但真如简单不简单!

小结

这篇博文到次算结束了,花了超过10个小时,下忙活的许久,又是看源码,又是翻API和网络搜索,到次也还是对很多东西陌生。嗯,String类在使用上是无比简单的,有任何一门语言的背景都可以快速的所有如构造函数、subString、trim等等,但是这只是简单的使用而言,虽然往往也足够了,但是总有一些不太那啥的感觉,我是有C背景的,C的背景对我的思维有很大的影响,等我翻看完String类之后突然就发现很多东西不再冲突了,而是很自然。看《你不知道的javaScript 上卷》时,作者谈论了一个像是道理的东西,大概这样描述:一项技术会且选择不用这是很正常的,毕竟实现方法多种多样,但是如果仅是因为不熟悉不会而选中去避开他回到一种固有的舒适区中去,这是不太恰当的,这样我们之后抱怨其他东西,如语言的设计者,但我们却没有真正理解语言如此设计的真正目的。

花了这么长时间来整理String类和写这篇博文,嗯,没有写好,后边遇见问题或有新感受的话还会填充这篇博文,我刚写这篇博文时脑袋中就闪过一个词:轮廓,嗯,这个词我很喜欢,看一样东西,学一块只是,不可仅仅跑马观花#手动尴尬:跑马观花#,也不可处处留心,仅看其大致的轮廓,寻几处有意思的点深入下去便可。

评论