从概念上讲,Java字符串就是Unicode字符序列。
字符串生成
在 Java 中,创建字符串有两种主要方式:
String x="hello";String y="hello";String z=new String("hello");String w=new String("hello");
内存分配机制
-
使用字符串字面量创建:
当你使用字符串字面量(如x、y
)创建字符串时,Java 会先检查字符串常量池(String Pool)中是否已经存在相同内容的字符串对象。如果存在,就直接返回常量池中该对象的引用;如果不存在,则在常量池中创建一个新的字符串对象,并返回其引用。 -
使用
new
关键字创建:
当使用new
关键字(如z、w
)创建字符串对象时,Java 会在堆内存中创建一个新的字符串对象,无论常量池中是否已经存在相同内容的字符串。也就是说,new
关键字会强制在堆上创建一个新的对象实例。
String x="hello";String y="hello";String z=new String("hello");String w=new String("hello");// 使用 == 比较引用System.out.println("x == y: " + (x == y)); // 输出 true,因为它们引用常量池中的同一个对象System.out.println("x == z: " + (x == z)); // 输出 false,因为 z 在堆上有新的对象System.out.println("z == w: " + (z == w)); // 输出 false,因为z 和 w 是堆上不同的对象
性能和资源使用
- 字符串字面量:由于字符串常量池的复用机制,使用字符串字面量可以节省内存,因为相同内容的字符串只会在常量池中存储一份。此外,从常量池中获取字符串对象的速度也比较快。
new
关键字:每次使用new
关键字都会在堆上创建一个新的对象,这会增加内存开销。如果频繁使用new
关键字创建相同内容的字符串,会导致不必要的内存浪费。
使用场景
- 字符串字面量:当你需要创建固定的、常用的字符串时,推荐使用字符串字面量,这样可以提高性能和节省内存。
new
关键字:当你需要确保每次都创建一个新的字符串对象,或者需要从其他字符数组或字节数组创建字符串时,可以使用new
关键字。例如:
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str = new String(charArray);
字符串拼接
与绝大多数的程序设计语言一样,Java语言允许使用+号连接(拼接)两个字符串。可以使用 +
运算符或者 concat()
方法来拼接字符串。
x=x+"demo";x=x+24+50;x=x+true;System.out.println(x);x= x.replace("o","li");//替换System.out.println(x);String[] arr=x.split("i");//间隔System.out.println(Arrays.toString(arr));
indexOf
在 Java 里,indexOf
是 String
类提供的一个实用方法,主要用于查找某个字符或者子字符串在当前字符串里首次出现的位置。此方法用于查找指定字符 ch
在字符串中首次出现的索引位置。若找到该字符,就返回其索引;若未找到,则返回 -1。
x=x+"demo";x=x+24+50;x=x+true;System.out.println(x);x= x.replace("o","li");//替换System.out.println(x);String[] arr=x.split("i");System.out.println(Arrays.toString(arr));int index=x.indexOf("dem");//返回下标System.out.println(index);int index1=x.indexOf("1dem");//返回下标System.out.println(index1);
substring
在 Java 中,substring
是 String
类提供的一个重要方法,用于从一个字符串中提取子字符串。
substring(int beginIndex, int endIndex)
用于提取从索引 beginIndex
开始(包含)到索引 endIndex
结束(不包含)的子字符串。即提取的子字符串包含 beginIndex
位置的字符,但不包含 endIndex
位置的字符。
x=x+"demo";x=x+24+50;x=x+true;System.out.println(x);x= x.replace("o","li");//替换System.out.println(x);String[] arr=x.split("i");System.out.println(Arrays.toString(arr));int index=x.indexOf("dem");//返回下标System.out.println(index);int index1=x.indexOf("1dem");//返回下标System.out.println(index1);System.out.println(x.substring(0,5));//左闭右开
空串与Null 串
在 Java 里,空串(Empty String)和 Null
串(null
String)是两个不同的概念。
定义和区别
- 空串:指的是长度为 0 的字符串,它是一个实际存在的字符串对象,只是其中不包含任何字符。在 Java 中,可以用双引号
""
来表示空串。 Null
串:null
并不是一个字符串对象,而是一个特殊的值,它表示变量没有引用任何对象。当一个字符串变量被赋值为null
时,意味着这个变量没有指向任何有效的字符串对象。
public class EmptyVsNullString {public static void main(String[] args) {// 定义空串String emptyString = "";// 定义 Null 串String nullString = null;// 输出空串的长度System.out.println("空串的长度: " + emptyString.length()); try {// 尝试输出 Null 串的长度,会抛出 NullPointerExceptionSystem.out.println("Null 串的长度: " + nullString.length()); } catch (NullPointerException e) {System.out.println("捕获到 NullPointerException: " + e.getMessage());}}
}
emptyString
是一个空串,调用 length()
方法可以正常返回 0;而 nullString
是 null
,调用 length()
方法会抛出 NullPointerException
异常,因为 null
不是一个有效的对象,不能调用其方法。
使用场景
- 空串:当你需要表示一个字符串为空,但它是一个合法的、有效的字符串对象时,就可以使用空串。例如,在表单验证中,如果用户没有输入任何内容,对应的字段可以用空串来表示。
Null
串:当你不确定一个字符串变量是否有值,或者需要表示某个字符串还未被初始化时,可以将其赋值为null
。例如,在数据库查询中,如果没有找到对应的记录,相关的字符串字段可能会被赋值为null
。
compareTo
在 Java 中,compareTo
是 String
类实现的一个重要方法,它用于按字典顺序比较两个字符串。
public class CompareToExample {public static void main(String[] args) {String str1 = "apple";String str2 = "banana";String str3 = "apple";int result1 = str1.compareTo(str2);int result2 = str1.compareTo(str3);System.out.println("str1 与 str2 比较结果: " + result1);System.out.println("str1 与 str3 比较结果: " + result2);}
}
在上述代码中,我们创建了三个字符串 str1
、str2
和 str3
,然后分别使用 compareTo
方法比较 str1
和 str2
、str1
和 str3
,并将比较结果存储在 result1
和 result2
中,最后输出比较结果。
返回值含义
compareTo
方法返回一个整数值,该值的含义如下:
- 返回值为 0:表示两个字符串相等。也就是说,两个字符串包含相同的字符序列,且字符的大小写和顺序都一致。
- 返回值小于 0:表示当前字符串(调用
compareTo
方法的字符串)在字典顺序上小于参数字符串。即当前字符串按字典顺序排在参数字符串之前。 - 返回值大于 0:表示当前字符串在字典顺序上大于参数字符串。即当前字符串按字典顺序排在参数字符串之后。
codepoints
在 Java 中,codepoints()
是 String
类在 Java 8 及以后版本引入的一个方法,它属于 Java 流(Stream)API 的一部分,用于将字符串中的每个字符转换为对应的 Unicode 代码点,并返回一个 IntStream
对象,这样就可以利用流的各种操作来处理代码点。
import java.util.stream.IntStream;public class CodepointsExample1 {public static void main(String[] args) {String str = "Hello😀";IntStream codePoints = str.codepoints();codePoints.forEach(codePoint -> System.out.println("Code point: " + codePoint));}
}
StringBuffer StringBuilder string
在 Java 里,String
、StringBuffer
和 StringBuilder
都和字符串操作相关,但它们在特性、使用场景等方面存在差异。 StringBuffer 多线程安全 慢 适合多线程 StringBuilder多线程不安全 适合单线程 快 String 不可变字符串,每次拼接创建新的String对象。与String相比创建新的String对象StringBuffer StringBuilder速度都快
String
类
- 不可变性:
String
对象一旦创建,其内容就不能被修改。若对String
对象进行拼接、替换等操作,实际上是创建了一个新的String
对象。 - 线程安全:由于
String
是不可变的,所以多个线程可以同时访问同一个String
对象,不会出现数据不一致的问题。 - 字符串常量池:Java 有字符串常量池,使用字符串字面量创建
String
对象时,如果常量池中已经存在相同内容的字符串,就会直接返回该字符串的引用,避免重复创建对象,节省内存。
public class StringExample {public static void main(String[] args) {String str1 = "Hello";String str2 = str1 + ", World!";System.out.println("str1: " + str1); System.out.println("str2: " + str2); }
}
使用场景
当字符串内容不需要频繁修改,且需要在多线程环境下共享时,适合使用 String
类。例如,存储配置信息、数据库连接字符串等。
StringBuffer
类
- 可变性:
StringBuffer
对象的内容可以被修改,它提供了一系列方法用于对字符串进行追加、插入、删除等操作。 - 线程安全:
StringBuffer
类的方法大多使用了synchronized
关键字进行同步,保证了在多线程环境下操作的线程安全性,但这也会带来一定的性能开销。
public class StringBufferExample {public static void main(String[] args) {StringBuffer sb = new StringBuffer("Hello");sb.append(", World!");System.out.println(sb.toString()); }
}
使用场景
当需要在多线程环境下对字符串进行频繁修改时,适合使用 StringBuffer
类。例如,在多线程的日志记录器中,可能会频繁追加日志信息。
StringBuilder
类
- 可变性:和
StringBuffer
一样,StringBuilder
对象的内容也可以被修改,同样提供了一系列用于操作字符串的方法。 - 非线程安全:
StringBuilder
类的方法没有使用synchronized
关键字进行同步,因此在多线程环境下使用可能会出现数据不一致的问题,但在单线程环境下,它的性能比StringBuffer
高。
public class StringBuilderExample {public static void main(String[] args) {StringBuilder sb = new StringBuilder("Hello");sb.append(", World!");System.out.println(sb.toString()); }
}
使用场景
当只在单线程环境下对字符串进行频繁修改时,适合使用 StringBuilder
类。例如,在循环中进行字符串拼接操作。
比较:
long start=System.currentTimeMillis();//时间戳1970.1.1StringBuilder res=new StringBuilder();for(int i=0;i<100000;i++) {res.append("a");}long end=System.currentTimeMillis();System.out.println("消耗时间"+(end-start));
// --------------------------------------long start1=System.currentTimeMillis();//时间戳1970.1.1String res1=" ";for(int i=0;i<100000;i++) {res1+="a";}long end1=System.currentTimeMillis();System.out.println("消耗时间"+(end1-start1));