String、StringBUffer、StringBUilder三者的异同点

String类

public final class String
extends Object
implements Serializable, Comparable, CharSequence

String的不可变性

private final char value[];//String底层是char数组
public class Test {
    public static void main(String[] args) {
        String str1 = "Hello";//字面量的定义,类似基本变量赋值
        String str2 = "hiWorld";
        str1 = "hi";//重新赋值
        System.out.println(str1);
        //以上面这种方式赋值都会保存到常量池中,而常量池当中不会存
        //相同的两个字符串的,他会先去找常量池里面有没有,有的话就会去复用了
        String str3 = "hi";
        str3 += "World";
        //这里str3是hi,与str1是相同的,他们是共用一块空间,但是str3进行了拼接
        System.out.println(str1);//输出str1,结果还是hi,说明并没有在原来空间上进行修改,故是str3是新开辟内存空间
        System.out.println(str3);//输出hiWorld,也是在常量池中开辟了一个新的空间,并非在原有空间上进行修改
        System.out.println("*********************");
        System.out.println(str2 == str3);//因为进行了拼接操作,所以开辟了新的内存空间,所以返回是false
        System.out.println("*********************");
        String str4 = "abc";
        //replace并没有改变char数组的长度,只是改变了其中一个字符的值,即使这样,也要必须重新开辟内存空间
        String str5 = str4.replace('a', 'c');
        System.out.println(str4);//abc
        System.out.println(str5);//cbc
        System.out.println(str5==str4);//false
    }
} 

不可变性

1、当对字符串进行重新赋值时,要重新开辟一块内存空间,不能再原有的字符串上进行修改。(一个char数组hello是5,一个char数组hi是2,不能直接修改原有的长度为五的char数组,因为是final修饰的,需要重新开辟一块空间去存储)

2、当对现有的字符串进行连接操作时,也需要重新制定内存区进行复制,不能对原有的char数组进行修改。

3、当调用String的replace()方法修改制定的字符或字符串时,也必须重新开辟内存空间,不能对原有的char数组进行修改。

String对象创建的几种方式

String str = “hello”;

String str1 = new String(); // 相当于一个this.value = new char[0];

String str2 = new String(String original); //相当于一个this.value = new value[original.length];

String str3 = new String(char[] a); //相当于this.value = Arrays.copyOf(value,value.length)全部复制

String str4 = new String(char[] a,int startIndex,int count);//部分复制,从index开始到index后面count个

String str1 = “abc”; 与String str2 = new String(“abc”);的区别?

package com.api.string;

public class StringTest2 {
    public static void main(String[] args) {
        //通过字面量的方式定义:此时s1和s2的数据声明在字符串常量池中
        String s1 = "Hello";
        String s2 = "Hello";
        //通过构造方法来赋值,此时s3和s4保存的地址值是数据开辟在堆空间中对应的地址值
        String s3 = new String("Hello");
        String s4 = new String("Hello");
        //构造方法赋值地址指向的是开辟在堆空间中的char数组的地址,
        //而char数组里面保存的又是常量池中的对应数据的地址,所以其实用的还是常量池中的数据
        System.out.println(s1 == s2);//true
        System.out.println(s1 == s3);//false
        System.out.println(s1 == s4);//false
        System.out.println(s3 == s4);//false
        User user1 = new User("Jack", 12);
        User user2 = new User("Jack", 12);
        User user3 = new User(new String("Rose"), 12);
        User user4 = new User(new String("Rose"), 12);
        System.out.println(user1 == user2); //false
        System.out.println(user1.equals(user2)); //false
        System.out.println(user1.name == user2.name); //true ,在User构造方法中采用的是字面量方式去赋值的
        System.out.println(user3.name == user4.name); //false, 使用构造方法则会在堆内存中开辟两个内存空间
    }
}

String str = new String(“Hello”);这种方式创建对象,在内存中创建了几个对象?

两个。 一个是在堆空间开辟的char数组,另一个是堆空间中char数组对应的常量池中的数据"Hello"。当然了,如果前面已经声明过"Hello",那么直接使用常量池中已有的即可。

String的拼接

public class StringTest {
    public static void main(String[] args) {
        String s1 = "Java";
        String s2 = "EE";

        String s3 = "JavaEE";
        String s4 = "Java" + "EE"; //通过加号进行字面量连接,与s3是相等的,还是在常量池中找
        String s5 = "s1" + "EE";//以下几种都是变量加字面量或者变量加变量
        String s6 = "Java" + s2;
        String s7 = s1 + s2;
        s6.intern();

        System.out.println(s3==s6);//false
        System.out.println(s3 == s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 == s6);//false
        System.out.println(s3 == s7);//false
        System.out.println(s4 == s7);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 == s7);//false
       
    }

}

结论:
常量与常量的拼接结果在常量池中,且常量池中不会存放相同内容的常量
只要拼接中有一个是变量,那么结果就在堆中,相当于new了
当然也可以对结果调用intern()方法,进行手动入池,返回值就在常量池中
如果用final修饰的变量名加上字面量,则结果还是在常量池当中,因为final修饰的变量则也是常量。

package com.api.string;

public class StringTest3 {
    public static void main(String[] args) {
        String s1 = "HelloWorld";
        String s2 = "Hello";
        String s3 = s2 + "World";
        System.out.println(s1 == s3);

        final String s4 = "Hello"; //final修饰的也是常量,也是字面量,入池的
        String s5 = s4 + "World";//相当于两个字面量相加,结果在常量池中
        System.out.println(s1 == s5);//true
    }
}

String类与基本数据类型与包装类之间的转换

String ----->基本数据类型、包装类:调用包装类的静态方法:parseXxx(str);

基本数据类型、包装类----->String:调用String重载的valueOf()方法,连接符也可以。

String和字符数组的转换

String---->char[];调用String的toCharArray()方法

char[]数组---->String:调用String 的构造器即可

String和字节数组之间的转换

String---->byte[] :调用getBytes()方法,可以在方法中指定字符集,无参则调用默认的字符集来编码。中文UTF-8占三个字节,GBK占2个字节

byte[]---->String:调用String构造方法

两个转换之间字符集一定要保持一致,不然会乱码。

String、StringBuffer、StringBuilder三者的异同

String:不可变的字符序列,底层使用char[]存储
StringBuffer:可变的字符序列,JDK1.0的时候就有了,效率低,线程安全,底层是继承自AbstractStringBuffer的char[],和String一样,但是没有final修饰,是可变的
StringBuilder:可变的字符序列,JDK1.5新增的,效率高,线程不安全,底层是继承自AbstractStringBuffer的char[],和String一样,但是没有final修饰,是可变的
这三个底层都是char[]存储,但是后两个是可变的,长度不是固定的

底层源码分析:

三者实例化底层实现:

	String str = new String();//char[] value = new char[0]
    String str1 = new String("abc");//char[] value = new char[]{'a','b','c'}
    StringBuffer sb1 = new StringBuffer();//char[] value = new char[16]
    //在StringBuffer空参调用了父类的方法,底层默认创建了一个长度是16的数组
     public StringBuffer() {
        super(16);
    }
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

StringBuffer的构造方法

 	
	/*
	无论怎样添加,变得都是底层数组的长度,所以是可变的
    而String底层的数组是死的,长度要想变必须重新创建
    */
	StringBuffer sb1 = new StringBuffer();//空参
    sb1.append("a");//相当于底层char数组 value[0] = 'a';
    sb1.append("b");//相当于底层char数组 value[1] = 'b';
    StringBuffer sb2 = new StringBuffer("abc");//有参
    //底层相当于 char[] value = new char["abc".length() + 16]
    public StringBuffer(String str) {
        super(str.length() + 16);// 在默认长度16的基础上加上参数的char数组长度
        append(str);
    }
   

问题1:为什么输出length是3而不是19

    StringBuffer sb1 = new StringBuffer("abc");
        sb1.setCharAt(0,'m');
        System.out.println(sb1);
        System.out.println(sb1.length());//3,并不是16+3=19
    //以下是StringBuffer的length()方法的源码
    public synchronized int length() {
            return count;//返回的是count!而不是value.length,count表示的是当前value数组中元素的个数
    }

问题2:如果添加的数据底层数组装不下了,如何扩容?

首先来看StringBuffer的append()方法,发现调用的是父类的append()方法。请看源码。

public AbstractStringBuilder append(String str) {
        if (str == null) //判断是否为空,为空直接返回null
            return appendNull();
        int len = str.length();//获取要添加数组的长度
        ensureCapacityInternal(count + len);//判断是否需要扩容
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

这里我们假设:

StringBuffer sb = new String();

sb.append(“a”);

sb.append(“abc”);

在sb.append(“abc”);之前假设我们已经在底层默认长度为16的char数组中添加了15个字符了,接下来要添加"abc",很显然,16已经装不下了。ensureCapacityInternal(count + len); 参数传进去的就是15+3=18,请接着往下看。

每次添加之前都要调用一次**ensureCapacityInternal(count + len);**方法,判断是否大于16,如果不大于,则直接添加。否则,请看源码。

private void ensureCapacityInternal(int minimumCapacity) {//18传入
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) { // 18 > 16  18是大于底层默认数组长度的
            value = Arrays.copyOf(value, // 进行数组拷贝返回新的char数组
                    newCapacity(minimumCapacity));//参数二调用
                   								 //了newCapacity(minimumCapacity)方法
        }
    }

接着调用newCapacity(minimumCapacity)方法;

private int newCapacity(int minCapacity) { //传入18
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;//newCapacity等于原有数组长度16*2+2		
        if (newCapacity - minCapacity < 0) {//如果扩容后还不够,那直接newCapacity直接就等于传进来的长度
            newCapacity = minCapacity;
        }
    //如果newCapacity不小于等于0并且不大于Integer.MAX_VALUE - 8,返回扩容后的值
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

所以当添加的数据底层数组装不下了,扩容至默认数组长度两倍+2,如果添加的字符串超出这个长度,则直接返回原本字符串长度加上需要添加的字符串长度,然后进行数组拷贝,返回新数组。

扩容举例

public static void main(String[] args) {
        StringBuffer sb1 = new StringBuffer();
        sb1.append("abcdefghijklmno");
        System.out.println(sb1.length());//15
        System.out.println(sb1.capacity());//16
        sb1.append("aa");
        System.out.println(sb1.length());//17
        System.out.println(sb1.capacity());//34
    }
public static void main(String[] args) {
        StringBuffer sb1 = new StringBuffer();
        sb1.append("abcdefghijklmnopabcdefghijklmnopaa");
        System.out.println(sb1.length());//34
        System.out.println(sb1.capacity());//34
        sb1.append("aa");
        System.out.println(sb1.length());//36
        System.out.println(sb1.capacity());//70

    }

StringBuffer和StringBuilder基本一致,前者使用的都是同步方法,而后者没有,其他基本相同。

如果在开发当中拼接十分频繁,不要使用String,优先使用StringBuffer和StringBuilder
在开发当中尽量使用代参的构造器:StringBuffered(int capacity) 这样指定容量。能够避免扩容,避免复制,也会提高性能。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页