String/StringBuilder/StringJoiner

一、String

(1)如何获取String对象?

1、直接赋值:String s = “123”;

2、使用new关键字结合不同的构造方法创建。

构造方法:

public String()创建一个空字符串对象
public String(String original)创建一个值为original的字符串对象
public String(char[] value)创建一个值为value的字符串对象
public String(byte[] value)创建一个值为value的字符串对象

关于空字符串:

类似于空集的概念,空有一个集合但里面什么都没装。

代码例子:

//直接赋值
String s1 = "abc";
//使用new关键字
//1.空参的构造方法
String s2 = new String();
//2.传字符串
String s3 = new String("abc");
//3.传字符数组
char[] value1 = {'a', 'b', 'c'};
String s4 = new String(value1);
//4.传byte数组
byte[] value2 = {97, 98, 99};
String s5 = new String(value2);
(2)String类中的方法

由于字符串对象一旦创建便不可再改变,所以下面这些方法都是返回一个新的字符串。

1、拼接:+,例如下面这个代码,

int num = 1;
System.out.println("这个数字是" + num);

在输出语句中经常将字符串和变量使用+进行拼接形成一个新的字符串。

2、比较:s1.equals(s2),比较s1和s2中存的内容是否相等。

3、截取:substring(6, 14),用来截取字符串,包左不包右。

4、替换:replace("TMD", "***"),对子字符串进行替换。

5、indexOf("world"):返回指定子字符串在原字符串第一次出现位置的索引。

(3)应用场景

二、StringBuilder

相当于一个容器,里面的内容是可变的。

1、如何创建一个StringBuilder对象?

new一个。

构造方法:

public StringBuilder()创建一个StringBuilder对象
public StringBuilder(String str)创建一个StringBuilder对象并将str添加到容器中
2、StringBuilder中的方法

(1)append(int/String/...):重载方法,添加数据,可以是任意数据类型;

(2)reverse():反转容器中的数据;

(3)toString():将StringBuilder对象转换为String对象;

(4)length():返回字符串的长度。

关于append方法的重载:

下面是具体代码:

//创建一个StringBuilder对象
StringBuilder sb = new StringBuilder();
//添加数据
sb.append(10).append('2');
//反转
sb.reverse();
//长度
int length = sb.length();
//toString:转换成String对象
String string = sb.toString();
3、应用场景

当字符串需要拼接和反转时用StringBuilder比较方便。

链式编程:

sb.append("aaa").append("bbb").toString().length(); 

三、StringJoiner

1、如何创建一个StringJoiner对象?

new一个。

构造方法:

public StringJoiner(s1)创建一个StringJoiner对象并指定分隔符为s1
public StringJoiner(String str)创建一个StringJoiner对象并指定分隔符为s1,以及字符串的开始符号为s2和结束符号为s3
2、StringJoiner中的方法

(1)add(int/String/...):添加数据;

(2)toString():将StringBuilder对象转换为String对象;

(3)length():返回字符串的长度。

3、应用场景

每次添加需要分隔符以及字符串的开始和结束需要符号时。

问题1:Java编译器在遇到字符串字面量时是怎么做的?

1、首先去字符串常量池里查看是否有这个字符串对象;

2、①如果有,直接将其引用赋给变量;

②如果没有,会自动在字符串常量池中创建一个包含其值的字符串对象,再将引用赋给变量。

问题2:Java在使用new关键字创建String对象时做了什么?

下面以这个String s = new String("abc");来解释:

1、首先检查字符串字面量abc在字符串常量池中是否存在;

2、①如果存在,则在堆中new一个字符串对象,存的值为abc;

②如果不存在,先在字符串常量池中创建一个包含其值的字符串对象,然后再在堆中new一个字符串对象,存的值为abc。

问题3:为什么String类的对象一旦创建便不可再变?

final修饰的变量

(1)基本数据类型:其值不可再变化;

(2)引用数据类型:指向的地址不会再变化,但地址中存的内容是可能会被改变的。

下面介绍一下JDK8及以前的String类源码:

private final char value[];

可以看到定义了一个字符数组value,用来存字符串。修饰符为final,value所记录的地址值不会再发生变化,private修饰符使外界无法访问到value所记录的地址值,而且在本类中也没有提供相应的方法去访问value所记录的地址值和修改value所指向的地址中存储的数据。

问题4:为什么String类中的方法都返回一个新的字符串?

下面看一下substring方法的源码,可以看到new String,所以是新创建了一个字符串。

public String substring(int beginIndex) {
    // 检查起始索引是否小于 0,如果是,则抛出 StringIndexOutOfBoundsException 异常
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    // 计算子字符串的长度
    int subLen = value.length - beginIndex;
    // 检查子字符串长度是否为负数,如果是,则抛出 StringIndexOutOfBoundsException 异常
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    // 如果起始索引为 0,则返回原字符串;否则,创建并返回新的字符串
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

问题5:使用+拼接字符串的原理

分为两种情况:

(1)字面量拼接:

String s = "a" + "b";

上面这行代码经过编译生成class文件,在还没有运行的时候,通过字符串优化机制s就已经是拼接之后的结果"ab"了,即

String s = "ab";

所以这时候就会应用问题1中的步骤了,在字符串常量池中进行查找,有则复用,没有则创建。

(2)有变量参与

如下面这行代码:

String s1 = "hello";
String result = s1 + "world";

由于Java是一门解释型语言,在拼接时会变为下面这行代码:

new StringBuilder().append(s1).append("world").toString()

下面来介绍一下StringBuilder中的toString()方法:

public String toString() {
    return new String(value, 0, count);
}

可以看到new了一个新的String对象,所以每次使用+进行字符串拼接时会创建两个对象,分别是StringBuilder和String对象。当拼接次数过多时资源浪费比较严重,所以可以使用StringBuilder进行拼接比较好。

问题6:StringBuilder底层原理

1、首先在使用空参构造函数new一个StringBuilder对象时就创建了一个容量为16的数组;

public StringBuilder() {
    super(16);
}

调用了AbstractStringBuilder类中带有一个参数的构造函数,可以看到创建一个长度为16的数组。

//定义一个数组,存放byte型数据
byte[] value;

//AbstractStringBuilder类带有一个参数的构造方法
AbstractStringBuilder(int capacity) {
    if (COMPACT_STRINGS) {
        //创建一个长度为16的数组
        value = new byte[capacity];
        coder = LATIN1;
    } else {
        value = StringUTF16.newBytesFor(capacity);
        coder = UTF16;
    }
}

2、当添加数据时,来看一下append方法:

public StringBuilder append(String str) {
    super.append(str);
    return this;
}

实际上是调用了AbstractStringBuilder类中的append方法,len表示要添加的字符串的长度,count表示value数组中已经存的字符个数,然后将count + len的值传入ensureCapacityInternal 方法。

public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
}

先来介绍一下容量和长度

①容量:最多可以存多少;

②长度:已经存了多少。

下面来看一下ensureCapacityInternal方法:

关注两个参数:minimumCapacity和oldCapacity。

minimumCapacity表示添加所需的最小容量,和数组的长度oldCapacity进行判断,如果minimumCapacity大于oldCapacity,表示放不下需要扩容。

private void ensureCapacityInternal(int minimumCapacity) {
        // oldCapacity表示没添加之前的容量
        int oldCapacity = value.length >> coder;
        //如果添加完之后的容量大于oldCapacity,则需要扩容
        if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }
}

 下面来看一下是如何扩容的: oldLength 表示数组的长度,newLength 表示所需的最小容量,
growth 表示需要数组需要增加的长度。

private int newCapacity(int minCapacity) {
        // oldLength 表示数组的长度
        int oldLength = value.length;
        // newLength 表示所需的最小容量
        int newLength = minCapacity << coder;
        // growth 表示需要增加的长度
        int growth = newLength - oldLength;
        int length = ArraysSupport.newLength(oldLength, growth, oldLength + (2 << coder));
        if (length == Integer.MAX_VALUE) {
            throw new OutOfMemoryError("Required length exceeds implementation limit");
        }
        return length >> coder;
}

比较growth和oldlength+2哪个大? 

如果growth大于oldlength+2,则证明扩容的长度oldlength+2是不够的,此时以growth为准。

public static int newLength(int oldLength, int minGrowth, int prefGrowth) {

        int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
        if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
            return prefLength;
        } else {
            // put code cold in a separate method
            return hugeLength(oldLength, minGrowth);
        }
    }

综上所述底层原理为:

①在创建StringBuilder对象时会初始化一个长度为16的数组;

②当添加字符串时,如果用于存储的数组长度不够时会扩容,扩容之后的数组长度是2*length+2;

如果扩容后的数组依然放不下时则会以实际添加的字符串长度为准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值