关于源码:StringString-BuilderString-Buffer源码

40次阅读

共计 5500 个字符,预计需要花费 14 分钟才能阅读完成。

[TOC]

String

String 是一个很一般的类

源码剖析

// 该值用于字符存储
private final char value[];

// 缓存字符串的哈希码
private int hash;// Default to 0

// 这个是一个构造函数
// 把传递进来的字符串对象 value 这个数组的值,// 赋值给结构的以后对象,hash 的解决形式也一样。public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
}



//String 的初始化有很多种
// 空参数初始化
//String 初始化
// 字符数组初始化
// 字节数组初始化
// 通过 StringBuffer,StringBuilder 结构

问题:我现正在筹备结构一个 String 的对象,那 original 这个对象又是从何而来?是什么时候结构的呢?

测试一下:

public static void main(String[] args) {String str = new String("zwt");
        String str1 =  new String("zwt");
}

在 Java 中,当值被双引号引起来(如本示例中的 ”abc”),JVM 会去先查看看一看常量池里有没有 abc 这个对象,

如果没有,把 abc 初始化为对象放入常量池,如果有,间接返回常量池内容。

Java 字符串两种申明形式在堆内存中不同的体现

为了防止反复的创建对象,尽量应用 String s1 =”123″ 而不是 String s1 = new String(“123”),因为 JVM 对前者给做了优化。

罕用的 API

System.out.println(str.isEmpty());// 判断是不是空字符串
System.out.println(str.length());// 获取字符串长度
System.out.println(str.charAt(1));// 获取指定地位的字符
System.out.println(str.substring(2, 3));// 截取指定区间字符串
System.out.println(str.equals(str1));// 比拟字符串

isEmpty()

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

length()

    public int length() {return value.length;}

charAt()

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

substring()

    public String substring(int beginIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen);
        }
        // 如果截取的开始范畴刚好是 0 并且完结范畴等于数组的长度,间接返回以后对象,// 否则用该数组和传入的开始范畴和完结范畴从新构建 String 对象并返回。return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

equals()

    public boolean equals(Object anObject) {
        // 如果是同一个援用,间接返回 true
        if (this == anObject) {return true;}
        // 判断是否是 String
        if (anObject instanceof String) {
            // 判断长度是否统一
            String anotherString = (String)anObject;
            int n = value.length;
            // 判断 char[]外面的每一个值是否相等
            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()与“==”

这两者之间没有必然的分割,

在援用类型中,”==” 是比拟两个援用是否指向堆内存里的同一个地址(同一个对象),

equals 是一个一般的办法,该办法返回的后果依赖于本身的实现

intern()

public native String intern();

// 如果常量池中有以后 String 的值,就返回这个值,如果没有就加进去,返回这个值的援用,

一些根底

Java 根本数据类型和援用类型

Java 中一共有 四类八种根本数据类型 ,除掉这四类八种根本类型,其它的都是 对象,也就是援用类型。

根本数据类型
浮点类型 float double
字符型 char
逻辑型 boolean
整型 byte short int long

Java 主动装箱 / 拆箱

Integer 外面咱们已经说过得 valueOf (),这个加上 valueOf 办法的过程,就是 Java 中常常说的装箱过程。

在 JDK1.5 中,给这四类八种根本类型退出了包装类。

第一类:整型
byte Byte
short Short
int Integer
long Long

第二类:浮点型
float Float
double Double

第三类:逻辑型
boolean Boolean

第四类:字符型
char Character

将 int 的变量转换成 Integer 对象,这个过程叫做装箱,

反之将 Integer 对象转换成 int 类型值,这个过程叫做拆箱。

以上这些装箱拆箱的办法是在编译成 class 文件时主动加上的,不须要程序员手工染指,因而又叫主动装箱 / 拆箱。

用途:

1、对象是对事实世界的模仿。

2、为泛型提供了反对。

3、提供了丰盛的属性和 API

public static void main(String[] args) {
        int int1 = 180;
        Integer int2 = new Integer(180);
}

体现如下图:

StringBuilder

StringBuilder 类被 final 所润饰,因而不能被继承。

StringBuilder 类继承于 AbstractStringBuilder 类。

实际上,AbstractStringBuilder 类具体实现了可变字符序列的一系列操作,

比方:append()、insert()、delete()、replace()、charAt()办法等。

值得一提的是,StringBuffer 也是继承于 AbstractStringBuilder 类。

StringBuilder 类实现了 2 个接口:

Serializable 序列化接口,示意对象能够被序列化。

CharSequence 字符序列接口,提供了几个对字符序列进行只读拜访的办法,

比方:length()、charAt()、subSequence()、toString()办法等。

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

定义的常量

//toString 返回的最初一个值的缓存。每当批改 StringBuffer 时革除。private transient char[] toStringCache;

AbstractStringBuilder

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    //value 用来存储字符序列中的字符。value 是一个动静的数组,当存储容量有余时,会对它进行扩容。char[] value;

    /**
     * The count is the number of characters used.
     */
    //count 示意 value 数组中已存储的字符数。int count;
    
    

构造方法

public StringBuilder() {super(16);
}

public StringBuilder(int capacity) {super(capacity);
}

public StringBuilder(String str) {super(str.length() + 16);
    append(str);
}

public StringBuilder(CharSequence seq) {this(seq.length() + 16);
    append(seq);
}

// AbstractStringBuilder.java
AbstractStringBuilder(int capacity) {value = new char[capacity];
}

StringBuilder 类提供了 4 个构造方法。构造方法次要实现了对 value 数组的初始化。

其中:

  1. 默认构造方法设置了 value 数组的初始容量为 16。
  2. 第 2 个构造方法设置了 value 数组的初始容量为指定的大小。
  3. 第 3 个构造方法承受一个 String 对象作为参数,设置了 value 数组的初始容量为 String 对象的长度 +16,并把 String 对象中的字符增加到 value 数组中。
  4. 第 4 个构造方法承受一个 CharSequence 对象作为参数,设置了 value 数组的初始容量为 CharSequence 对象的长度 +16,并把 CharSequence 对象中的字符增加到 value 数组中。

append()办法

有多种实现,个别的程序为:

append() —-> ensureCapacityInternal() 确保 value 数组有足够的容量 —-> newCapacity()新的容量

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }



    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        
        // 扩容参数
        
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {newCapacity = minCapacity;}
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

在下面代码中能够看到具体的扩容规定是 * 2 + 2

StringBuffer

根本就是加了一个 synchronized 的 StringBuilder。

StringBuilder 和 StringBuffer 实用的场景是什么?

stringbuffer 诚然是线程平安的,stringbuffer 诚然是比 stringbuilder 更慢,诚然,在多线程的状况下,实践上是应该应用线程平安的 stringbuffer 的。

实际上根本没有什么中央显示你须要一个线程平安的 string 拼接器。

stringbuffer 根本没有实用场景,你应该在所有的状况下抉择应用 stringbuiler,除非你真的遇到了一个须要线程平安的场景。

如果你遇见了,,,

stringbuffer 的线程平安,仅仅是保障 jvm 不抛出异样顺利的往下执行而已,它可不保障逻辑正确和调用程序正确。大多数时候,咱们须要的不仅仅是线程平安,而是锁。

最初,为什么会有 stringbuffer 的存在,如果真的没有价值,为什么 jdk 会提供这个类?

答案太简略了,因为最早是没有 stringbuilder 的,sun 的人不知处于何种思考,决定让 stringbuffer 是线程平安的,

于是,在 jdk1.5 的时候,终于决定提供一个非线程平安的 stringbuffer 实现,并命名为 stringbuilder。

顺便,javac 如同大略也是从这个版本开始,把所有用加号连贯的 string 运算都隐式的改写成 stringbuilder,

也就是说,从 jdk1.5 开始,用加号拼接字符串曾经简直没有什么性能损失了。

扩大小常识

Java9 改良了字符串(包含 String、StringBuffer、StringBuilder)的实现。

在 Java9 以前字符串采纳 char[]数组来保留字符,因而字符串的每个字符占 2 字节,

而 Java9 的字符串采纳 byte[]数组再加一个 encoding-flag 字段来保留字符,因而字符串的每个字符只占 1 字节。

所以 Java9 的字符串更加节俭空间,字符串的性能办法也没有受到影响。

参考链接

https://zhuanlan.zhihu.com/p/…

https://blog.csdn.net/u012317…

https://www.zhihu.com/questio…

正文完
 0