Java 字符串String

本文深入探讨了Java中的String类,强调其不可变性,详细阐述了字符串的字面量赋值与new方式赋值的内存区别,并讨论了字符串拼接的各种方式及其效率。此外,还介绍了可变的字符序列类StringBuffer和StringBuilder,重点讨论了它们的构造器、扩容机制和线程安全性。最后,进行了三者之间的效率比较。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. String 类

不可变的字符序列,底层用char[] 存储。

1.1 类介绍

package java.lang;
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ 
    ...
}

1)String 类被final 修饰,不能被继承;
2)String 类实现Serializable 接口,表示字符串支持序列化;
3)String 类实现Comparable 接口,表示字符串可以比较大小;

1.2 不可变性

private final char value[];

字符串底层使用char[] 存储数据,用final 所修饰,说明数组内容不可被修改,即String 是不可变的字符序列

1.2.1 字面量赋值

	String s1 = "JavaEE";

通过字面量的方式(不同于new)给一个字符串赋值,此时的字符串值声明在方法区字符串常量池中,栈中存放的是字符串在常量池中的地址;

<创建一个对象:字符串常量池中>
栈(常量池地址) ⇒ 字符串常量池(字符串值)

注意:字符串常量池中不会存放两个相同内容的字符串,无则新建,有则复用;

1.2.2 new 方式赋值

	String s3 = new String("JavaEE");

通过new 在堆内存中开辟内存空间,栈中存放该对象的堆内存地址,而真正的字符串值仍旧存储在字符串常量池中,其实堆中的内存空间存放的是字符串常量池的地址!

<创建两个对象:堆和字符串常量池创建对象>
栈(堆地址) ⇒ 堆(常量池地址) ⇒ 字符串常量池(字符串值)

1.3 字符串拼接

1.3.1 字面量拼接

	String s1 = "abc";
	String s2 = "def";
	String s3 = "abcdef";
	String s4 = "abc"+"def";
	
	System.out.println(s3 == s4); // true

字面量拼接,值存放在字符串常量池中,相同值复用,s3与s4 地址相同,输出true

<创建一个对象:字符串常量池中>
栈(常量池地址) ⇒ 字符串常量池(字符串值)

1.3.2 变量名参与的拼接

	String s1 = "abc";
	String s2 = "def";
	String s3 = "abcdef";
	String s5 = s1 + "def";
	String s6 = s1 + s2;
	
	System.out.println(s3 == s5); // false
	System.out.println(s3 == s6); // false

变量名参与的拼接,相当于new ,需要在堆中开辟内存空间,存放字符串常量池中的地址,值仍旧存放在字符串常量池中

<创建两个对象:堆和字符串常量池创建对象>
栈(堆地址) ⇒ 堆(常量池地址) ⇒ 字符串常量池(字符串值)

1.3.3 常量名参与拼接

	final String s1 = "abc";
	String s2 = "def";
	String s3 = "abcdef";
	String s5 = s1 + "def";
	String s6 = s1 + s2;
	
	System.out.println(s3 == s5); // true
	System.out.println(s3 == s6); // false

只有变量名参与拼接,才相当于相当于new,常量拼接与字面量拼接相同,只在常量池中创建对象

<创建一个对象:字符串常量池中>
栈(常量池地址) ⇒ 字符串常量池(字符串值)

1.3.4 intern()

	String s1 = "abc";
	String s1 = "abc";
	String s2 = "def";
	String s3 = "abcdef";
	String s6 = s1 + s2;
	String s7 = s6.intern();
	
	System.out.println(s3 == s7); // true

s6.intern() 返回的s7只在常量池中声明,所以为true

1.4 常用方法

public class StringMethodTest {
    @Test
    public void test1(){
        String str = "HelloWorld";
        System.out.println(str.length());
        System.out.println(str.charAt(0)); // 根据索引取值
        System.out.println(str.charAt(9));
        System.out.println(str.isEmpty());

        System.out.println(str.toLowerCase()); // 转小写
        System.out.println(str.toUpperCase()); // 转大写

        String str1 = " a b c ";
        System.out.println(str1.trim()); // 去除首尾空格

        System.out.println(str.equals(str1)); // 比较内容
        System.out.println(str.equalsIgnoreCase(str1)); // 比较内容(忽略大小写)

        System.out.println(str.concat("Java")); // 拼接
        System.out.println("a".compareTo("b")); // 比较大小(可用于字符串排序)

        System.out.println(str.substring(5));
        System.out.println(str.substring(0,5));

        System.out.println(str.startsWith("H"));
        System.out.println(str.startsWith("e",1));
        System.out.println(str.endsWith("ld"));

        System.out.println(str.contains("ll"));

        System.out.println(str.indexOf("Hello")); // 找索引
        System.out.println(str.indexOf("llo",5)); // 找索引(指定位置开始)
        System.out.println(str.lastIndexOf("llo")); // 从后往前找索引
        System.out.println(str.indexOf("llo",6));

        System.out.println(str.replace("e","o")); // 替换字符
        System.out.println(str.replace("ll","ooo")); // 替换字符串
        System.out.println("a5b5c5d5".replaceAll("\\d+","-")); // 正则
        System.out.println("a5b5c5d5".replaceFirst("\\d+","-")); // 正则(只替换第一个)

        System.out.println("123456".matches("\\d+")); // 正则匹配

        System.out.println("a|b|c".split("|")); // 切割字符串,返回数组
    }
}

1.5 类型转换

1.5.1 与基本类型、包裹类转换

    @Test
    public void test1(){
        // 1. String 转基本类型:包装类.parseXxx(str)
        String str1 = "123";
        int num = Integer.parseInt(str1);

        // 2. 基本数据类型、包装类转String,String.valueOf(xxx)
        String str2 = String.valueOf(num);
        String str3 = num + "";
    }

1.5.2 与char[] 转换

    @Test
    public void test1(){
        // 1. String 转char[]
        char[] chars = str1.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            System.out.println(chars[i]);
        }

        // 2.1 char[] 转String
        char[] arr = new char[]{'j','a','v','a'};
        String str4 = new String(arr);
        System.out.println(str4);
		// 2.2 char[] 转String,指定长度
		String str5 = new String(arr,0,2);
        System.out.println(str5);
    }

1.5.3 与byte[] 转换

    @Test
    public void test2() throws UnsupportedEncodingException {
        // 1.字符串转字节(编码) getBytes()
        String str = "abc中国";

        byte[] bytes = str.getBytes(); // 默认字符集
        System.out.println(Arrays.toString(bytes)); // UTF-8 一个汉字三个字节

        byte[] gbks = str.getBytes("gbk");
        System.out.println(Arrays.toString(gbks)); // gbk 一个汉字两个字节

        // 1.2 字节转字符串(解码) String 构造器
        String str2 = new String(bytes);
        System.out.println(str2);

        String str3 = new String(gbks); // 字符集不一致会乱码
        System.out.println(str3);

        String str4 = new String(gbks,"gbk"); // 字符集保持一致
        System.out.println(str4);
    }

2. StringBuffer 类

可变的字符序列,线程安全的(synchronized),效率偏低
多个线程操作共享数据的时候,该共享数据用需要用StringBuffer修饰;

2.1 类介绍

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{ 
    ...
}

1)String 类被final 修饰,不能被继承;
2)String 类实现Serializable 接口,表示字符串支持序列化;

2.2 构造器

2.2.1 无参构造器

StringBuffer buffer = new StringBuffer();

StringBuffer :

    public StringBuffer() {
        super(16);
    }

super(16):

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

底层创建一个16位的字符数组:char[] value = new char[16]

2.2.2 有参构造器

StringBuffer buffer2 = new StringBuffer("abc");

StringBuffer :

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

创建一个在"abc" 长度基础上+16 的字符数组

2.3 扩容底层数组

利用构造器初始化了底层数组的长度,但值不断增加,长度超过初始长度时,如何扩大容量?

	// 初始化16位字符数组
	StringBuffer buffer1 = new StringBuffer();
	// 追加20位
    buffer1.append("12345678901234567890"); 

append:

    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

super.append(str):

    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;
    }

ensureCapacityInternal(count + len):
当追加内容长度 + 原有值长度 > 数组长度时,根据总长度新建数组,将新增内容及原有内容一同复制到新数组中,实现扩容;

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

newCapacity(minimumCapacity):
新数组的长度为总长度 X 2 + 2

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;
    }

相较于String 每次都要新开辟内存空间再存值来说,效率更高;

2.4 指定底层数组长度

由于扩容的过程,仍然需要进行新内存空间的开辟,并且要对数组内容进行复制,所以可以提前指定底层数组长度,从而减少扩容,提升效率

StringBuffer buffer3 = new StringBuffer(100);

2.5 常用方法

@Test
public void test1(){
	StringBuffer buffer1 = new StringBuffer("abc");
	buffer1.append("123");
	System.out.println(buffer1);
	
	buffer1.delete(2,4);
	System.out.println(buffer1);
	
	buffer1.replace(0,2,"Hello");
	System.out.println(buffer1);
	
	buffer1.insert(5,false);
	System.out.println(buffer1);
	
	System.out.println(buffer1.reverse()); // 反转
	
	buffer1.append("123").append("456").append("789"); // 方法链写法
	System.out.println(buffer1.toString());
}

输出:
abc123
ab23
Hello23
Hellofalse23
32eslafolleH
32eslafolleH123456789

3. StringBuilder 类(JDK5.0)

StringBuilder 类与StringBuffer 类都是继承与同一个类(AbstractStringBuilder),功能相同,但不同点在于:
没用synchronized 修饰方法,线程不安全,但效率高,所以在不涉及线程安全问题,推荐使用StringBuilder ;

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

4. 三者效率比较

String 、StringBuffer 、StringBuilder 执行效率比较

public void test2(){
	long startTime = 0;
	long endTime = 0;
	
	String str = "";
	StringBuffer buffer = new StringBuffer();
	StringBuilder builder = new StringBuilder();
	
	// 字符串
	startTime = System.currentTimeMillis();
	for (int i = 0; i < 20000; i++) {
	   str = str + i;
	}
	endTime = System.currentTimeMillis();
	System.out.println("String 执行时间:" + (endTime - startTime));
	
	// StringBuffer
	startTime = System.currentTimeMillis();
	for (int i = 0; i < 20000; i++) {
	   buffer.append(i);
	}
	endTime = System.currentTimeMillis();
	System.out.println("StringBuffer 执行时间:" + (endTime - startTime));
	
	// StringBuilder
	startTime = System.currentTimeMillis();
	for (int i = 0; i < 20000; i++) {
	   builder.append(i);
	}
	endTime = System.currentTimeMillis();
	System.out.println("StringBuilder 执行时间:" + (endTime - startTime));
}

输出:
String 执行时间:2009
StringBuffer 执行时间:3
StringBuilder 执行时间:1

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值