J2SE入门(三) String、StringBuffer、StringBuilder

J2SE入门(三) String、StringBuffer、StringBuilder

String可以说是Java中使用最多最频繁的类,也是面试中会经常被问到的知识点。

一. String的使用

1. String的不可变性

/**
 * The {@code String} class represents character strings. All
 * string literals in Java programs, such as {@code "abc"}, are
 * implemented as instances of this class.
 * <p>
 * Strings are constant; their values cannot be changed after they
 * are created. String buffers support mutable strings.
 * Because String objects are immutable they can be shared. For example:
 * ...
 */

public final class String { 
    private final char value[];
}

String对象一旦在堆中创建出来,就无法再修改。因为String对象放在char数组中,该数组由final关键字修饰,不可变。

2. 定义一个字符串

/**
 * 定义一个字符串
 */
String str1 = "helloworld";

//也可以,但基本不这样写
String str2 = new String("helloworld");

上面的这句代码在内存中怎么体现?

3. 再次赋值给已定义的字符串

str1 = "helloChina";

我们开始已经说了String是由final关键字修饰,不可变,那么此时在内存中如何体现呢?

4. 使用变量来赋值变量

String str3 = str1;

这个时候str3和str1引用了相同的对象“helloChina”

5. String 对 “+” 的处理

String str4 = "good good" + " study";

通过编译工具后得到

String str4 = "good good study";

因此我们可以发现编译器在编译期间就是进行变量合并,而不会在内存中创建三个对象 “good good”,“ study”,"good good study"。但若是:

String str5 = str3 + "good good study";

这种情况无法在编译期间就进行变量合并。

6. String常用的方法

//str1 == "hello,world ";

//获取长度
str1.length()//12;

//截取位置2到5之间的字符串(包括位置2,不包括位置5,从0开始)
str1.substring(2,5);//"llo"

//判断是否含有字符串“ello”
str1.contains("ello");//true,通过indexOf实现

//获取ello在str1中的开始位置
str1.indexOf("ello");//1

//将字符串转化为字符串数据
str1.split(",");//["hello","world"]

//去掉字符串两侧空格
str1.trim();//"hello,world"

二. StringBuffer与StringBuilder

StringBuilder与StringBuffer作用就是用来处理字符串,但String类本身也具备很多方法可以用来处理字符串,那么为什么还要引入这两个类呢?看下面的例子

public static void main(String[] args) {
    String str0 = "hel,lo,wor,l,d";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++){
        str0 += i;
    }
    System.out.println(System.currentTimeMillis() - start);

    StringBuilder sb = new StringBuilder("hel,lo,wor,l,d");
    long start1 = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++){
        sb.append(i);
    }
    System.out.println(System.currentTimeMillis() - start1);

    StringBuffer sbf = new StringBuffer("hel,lo,wor,l,d");
    long start2 = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++){
        sbf.append(i);
    }
    System.out.println(System.currentTimeMillis() - start2);
}

上述代码中3处循环完成了同样的功能,字符串拼接,执行的结果如下:

36823
3
4

可以看出执行时间差别太大,为了解决String不擅长的大量字符串拼接这种业务场景,引入了StringBuffer和StringBuilder.

String StringBuffer StringBuilder
final修饰,不可继承 final修饰,不可继承 final修饰,不可继承
字符串常量,创建后不可变 字符串变量,可动态修改 字符串变量,可动态修改
不存在线程安全问题 线程安全,所有public方法由synchronized修改 线程不安全
大量字符串拼接效率最低 大量字符串拼接效率非常高 大量字符串拼接效率最高

StringBuffer与StringBuilder实现非常类似,下面以StringBuilder简单说明一下append()方法基本原理

1. 首先创建一个StringBuilder

StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder(100);

StringBuilder对字符串的操作是通过char[]来实现的,通过默认构造器创建的StringBuilder,其内部创建的char[]的默认长度为16,当然可以调用重载的构造器传递初始长度(推荐这样,因为这样可以减少数组扩容次数,提高效率)。

/**
 * Constructs a string builder with no characters in it and an
 * initial capacity of 16 characters.
 */
public StringBuilder() {
    super(16);
}

2. StringBuilder的append()方法

每次调用append(str)方法时,会首先判断数组长度是否足以添加传递来的字符串

/**
 * Appends the specified string to this character sequence.
 * <p>
 * The characters of the {@code String} argument are appended, in
 * order, increasing the length of this sequence by the length of the
 * argument. If {@code str} is {@code null}, then the four
 * characters {@code "null"} are appended.
 *
 * @param   str   a string.
 * @return  a reference to this object.
 */
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}
/**
 * For positive values of {@code minimumCapacity}, this method
 * behaves like {@code ensureCapacity}, however it is never
 * synchronized.
 * If {@code minimumCapacity} is non positive due to numeric
 * overflow, this method throws {@code OutOfMemoryError}.
 */
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

如果传递的字符串长度 + 数组已存放的字符的长度 > 数组的长度,这时就需要进行数据扩容了

/**
 * Returns a capacity at least as large as the given minimum capacity.
 * Returns the current capacity increased by the same amount + 2 if
 * that suffices.
 * Will not return a capacity greater than {@code MAX_ARRAY_SIZE}
 * unless the given minimum capacity is greater than that.
 *
 * @param  minCapacity the desired minimum capacity
 * @throws OutOfMemoryError if minCapacity is less than zero or
 *         greater than Integer.MAX_VALUE
 */
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”,但如果按此规则扩容后的数组也不足以添加新的字符串,就需要将数组长度设置为“数组内字符长度 + 传递的字符串长度”。

因此假如我们知道拼接的字符串大概长度有100多字符,我们就可以设置初始长度150或200,这样就可以避免或减少数组扩容的次数,从而提高效率。

三. 总结

本文对String的不可变性、基本原理和内存堆栈情况的分析、常用的方法,StringBuffer与StringBuilder的创建,append方法的原理讲解,对比了String、StringBuffer与StringBuilder异同,若有不对之处,请批评指正,谢谢!

猜你喜欢

转载自www.cnblogs.com/LiaHon/p/11056392.html