String

String类

java.lang.String类代表字符串。

字符串的特点:

  • 字符串的内容永不可变。【重点】

  • 正是因为字符串不可改变,所以字符串是可以共享使用的。

  • 字符串效果上相当于是char[]字符数组,但是底层原理是byte[]字节数组。

String 为什么是不可变的?

String 类中使用 final 关键字修饰字符数组来保存字符串,所以String 对象是不可变的。

修正:我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。

String 真正不可变有下面几点原因:

  1. 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
  2. String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
1
2
3
4
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char[] value;
//...
}

String类final + char[] private

字符串常量池

双引号创建

程序当中直接写上的双引号字符串,就在字符串常量池中,只有用””创造的字符串,才创建在常量池中

字符串常量池的作用:由于字符串常量池的存在,相同的字符串常量在内存中只会存在一份,这对于节省内存、提高性能以及避免错误是非常有益的。

实际上字符串常量池 HashTable 采用的是数组加链表的结构,链表中的节点是一个个的 HashTableEntry而 HashTableEntry 中的 value 则存储了堆上 String 对象的引用

image-20240323205421415

使用字面量声明 String 对象时,”也就是被双引号包围的字符串,在堆上创建对象,并驻留到字符串常量池中(注意这个用词)

调用 intern()方法,当字符串常量池没有相等的字符串时,会保存该字符串的引用

我们在上面用到了一个词驻留,这里对它进行一下规范。当我们说驻留一个字符串到字符串常量池时,指的是创建 HashTableEntry ,再使它的 value 指向堆上的 String 实例,并把 HashTableEntry 放入字符串常量池,而不是直接把 String 对象放入字符串常量池中。简单来说,可以理解为将 String 对象的引用保存在字符串常量池中。

new创建

1
2
3
4
5
String s1 = new string("Hydra");
String s2 = s1.intern();
System.out.println(s1 == s2); // f
System.out.println(s1 =="Hydra"); //f
System.out.println(s2 =="Hydra"); //t
  • 对于引用类型来说,**==是进行地址值的比较**
  • 若要进行内容比较,使用equals方法
  • 使用new创建的String对象在堆中,底层使用的是private final byte[] value,value的地址指向常量池
image-20240323204935711

在创建 s1 的时候,其实堆里已经创建了两个字符串对象 stringobject1 和 stringobject2 ,并且在字符串常量池中驻留了 stringobject2

当执行 s1.intern()方法时,字符串常量池中已经存在内容等于”Hydra”的字符串 stringobject2 ,直接返回这个引用并赋值给 s2

s1 和 s2 指向的是两个不同的 String 对象,因此返回 false

s2 指向的就是驻留在字符串常量池的 stringobject2,因此 s2==”Hydra”为 true,而 s1 指向的不是常量池中的对象引用所以返回 false

String的intern 方法有什么作用?

String.intern() 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:

  • 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
  • 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。

示例代码(JDK 1.8) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); //true

String s1 = new String(“abc”); 这句话创建了几个字符串对象?

会创建 1 或 2 个字符串对象。

1、如果字符串常量池中不存在字符串对象“abc”的引用,那么它会在堆上创建两个字符串对象,其中一个字符串对象的引用会被保存在字符串常量池中。

示例代码(JDK 1.8):

1
String s1 = new String("abc");

对应的字节码:

img

ldc 命令用于判断字符串常量池中是否保存了对应的字符串对象的引用,如果保存了的话直接返回,如果没有保存的话,会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。

2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。

示例代码(JDK 1.8):

1
2
3
4
// 字符串常量池中已存在字符串对象“abc”的引用
String s1 = "abc";
// 下面这段代码只会在堆中创建 1 个字符串对象“abc”
String s2 = new String("abc");

对应的字节码:

img

这里就不对上面的字节码进行详细注释了,7 这个位置的 ldc 命令不会在堆中创建新的字符串对象“abc”,这是因为 0 这个位置已经执行了一次 ldc 命令,已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 ldc 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。

String 类型的变量和常量做“+”运算时发生了什么?

先来看字符串不加 final 关键字拼接的情况(JDK1.8):

1
2
3
4
5
6
7
8
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing"; //可以常量折叠
String str4 = str1 + str2; //不能常量折叠,除非加final
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。

在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化。

对于 String str3 = "str" + "ing"; 编译器会给你优化成 String str3 = "string";

并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:

  • 基本数据类型( bytebooleanshortcharintfloatlongdouble)以及字符串常量。
  • final 修饰的基本数据类型和字符串变量
  • 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )

引用的值在程序编译期是无法确定的,编译器无法对其进行优化。

对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

1
String str4 = new StringBuilder().append(str1).append(str2).toString();

我们在平时写代码的时候,尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer

不过,字符串使用 final 关键字声明之后,可以让编译器当做常量来处理。

示例代码:

1
2
3
4
5
6
final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true

final 关键字修饰之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。

如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化。

示例代码(str2 在运行时才能确定其值):

1
2
3
4
5
6
7
8
final String str1 = "str";
final String str2 = getStr();
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 在堆上创建的新的对象
System.out.println(c == d);// false
public static String getStr() {
return "ing";
}

String中内容的比较相关方法

如果确实需要字符串的内容比较,可以使用两个方法:

**public boolean equals(Object obj)**:参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给true;否则返回false。

注意事项:

  • 任何对象都能用Object进行接收。

  • equals方法具有对称性,也就是a.equals(b)和b.equals(a)效果一样。

  • 如果比较双方一个常量一个变量,推荐把常量字符串写在前面。

    • 推荐:”abc”.equals(str)
    • 不推荐:str.equals(“abc”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String str1 = "Hello";

String str2 = "Hello";

char[] charArray = {'H', 'e', 'l', 'l', 'o'};

String str3 = new String(charArray);

System.out.println(str1.equals(str2)); // true

System.out.println(str2.equals(str3)); // true

System.out.println(str3.equals("Hello")); // true

System.out.println("Hello".equals(str1)); // true

**public boolean equalsIgnoreCase(String str)**:忽略大小写,进行内容比较。

1
2
3
4
String strA = "Java";
String strB = "java";
System.out.println(strA.equals(strB)); // false,严格区分大小写
System.out.println(strA.equalsIgnoreCase(strB)); // true,忽略大小写

public int compareTo(String anotherString):按字典顺序比较两个字符串。

返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的长度差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。

  • 如果参数字符串等于此字符串,则返回值 0;
  • 如果此字符串小于字符串参数,则返回一个小于 0 的值;
  • 如果此字符串大于字符串参数,则返回一个大于 0 的值。

说明:

如果第一个字符和参数的第一个字符不等,结束比较,返回第一个字符的ASCII码差值。如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至不等为止,返回该字符的ASCII码差值。 如果两个字符串不一样长,可对应字符又完全一样,则返回两个字符串的长度差值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String str1 = "Strings";
String str2 = "Strings";
String str3 = "Strings123";
String str4 = "Atring";

int result = str1.compareTo(str2);//0
System.out.println(result);

result = str2.compareTo; //-3
System.out.println(result);

result = str3.compareTo//3
System.out.println(result);

result = str4.compareTo(str3);
System.out.println(result); //-18,A的ASCII是65,S的ASCII是83

String的相关方法

String的构造方法

1
2
3
4
5
6
7
8
9
String s1 = new String();

String s2 = new String(String original);

String s3 = new String(char[] a);

String s4 = new String(char[] a,int startIndex,int count)

String s5 = new String(byte[] b)

String中与获取相关方法

**public int length()**:获取字符串当中含有的字符个数,拿到字符串长度

1
2
int length = "asdasfeutrvauevbueyvb".length();
System.out.println("字符串的长度是:" + length); //21

public String concat(String str):将当前字符串和参数字符串拼接成为返回值新的字符串

1
2
3
4
5
6
7
8
// 拼接字符串
String str1 = "Hello";
String str2 = "World";

String str3 = str1.concat(str2);
System.out.println(str1); // Hello,原封不动
System.out.println(str2); // World,原封不动
System.out.println(str3); // HelloWorld,新的字符串

**public char charAt(int index)**:获取指定索引位置的单个字符。(索引从0开始)

1
2
char ch = "Hello".charAt(1);
System.out.println("在1号索引位置的字符是:" + ch); //e

**public int indexOf(String str)**:查找参数字符串在本字符串当中首次出现的索引位置,如果没有返回-1值

1
2
3
4
String original = "HelloWorldHelloWorld";
int index = original.indexOf("llo");
System.out.println("第一次索引值是:" + index); // 2
System.out.println("HelloWorld".indexOf("abc")); // -1

String中与截取相关方法

**public String substring(int index)**:截取从参数位置一直到字符串末尾,返回新字符串

1
2
3
4
String str1 = "HelloWorld";
String str2 = str1.substring(5);
System.out.println(str1); // HelloWorld,原封不动
System.out.println(str2); // World,新字符串

**public String substring(int begin, int end)**:截取从begin开始,一直到end结束,中间的字符串。

备注:[begin,end),包含左边,不包含右边

1
2
3
String str1 = "HelloWorld";
String str3 = str1.substring(4, 7);
System.out.println(str3); // oWo

public String trim()

返回字符串的副本,忽略前导空白和尾部空白。

String的比较

返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的长度差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。

  • 如果参数字符串等于此字符串,则返回值 0;
  • 如果此字符串小于字符串参数,则返回一个小于 0 的值;
  • 如果此字符串大于字符串参数,则返回一个大于 0 的值。

说明:

如果第一个字符和参数的第一个字符不等,结束比较,返回第一个字符的ASCII码差值。

如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至不等为止,返回该字符的ASCII码差值。 如果两个字符串不一样长,可对应字符又完全一样,则返回两个字符串的长度差值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {

public static void main(String args[]) {
String str1 = "Strings";
String str2 = "Strings";
String str3 = "Strings123";

int result = str1.compareTo( str2 );
System.out.println(result);

result = str2.compareTo( str3 );
System.out.println(result);

result = str3.compareTo( str1 );
System.out.println(result);
}
}

以上程序执行结果为:

1
2
3
0
-3
3

String中与数组转换相关方法

**public char[] toCharArray()**:将当前字符串拆分成为字符数组作为返回值

1
2
3
4
// 转换成为字符数组
char[] chars = "Hello".toCharArray();
System.out.println(chars[0]); //H
System.out.println(chars.length); //5,数组的length没有括号

**public byte[] getBytes()**:获得当前字符串底层的字节数组

1
2
3
4
byte[] bytes = "abc".getBytes();
for (int i = 0; i < bytes.length; i++) {
System.out.println(bytes[i]); //97 98 99
}

字符串数组与字符串的转换

  • public static String valueOf(char[] data) :返回 char 数组参数的字符串表示形式
  • **public static String valueOf(char[] data, int offset, int count)**: 返回 char 数组参数的特定子数组的字符串表示形式,offset 参数是子数组的第一个字符的索引,count 参数指定子数组的长度。

String中分割与替换

**public String replace(CharSequence oldString, CharSequence newString)**:

将所有出现的老字符串替换成为新的字符串,返回替换之后的结果新字符串。

备注:CharSequence意思就是说可以接受字符串类型

1
2
3
4
String lang1 = "会不会玩儿呀!你大爷的!你大爷的!你大爷的!";
String lang2 = lang1.replace("你大爷的", "****");
System.out.println(lang2); // 会不会玩儿呀!****!****!****!

public String[] **split(String regex)**:按照参数的规则,将字符串切分成为若干部分。

注意事项:返回的是字符串数组

split方法的参数其实是一个“正则表达式”

注意:如果按照英文句点“.”进行切分,必须写”\.”(两个反斜杠)

1
2
3
4
5
6
7
8
9
10
11
12
String str1 = "aaa,bbb,ccc";
String[] array1 = str1.split(",");
for (int i = 0; i < array1.length; i++) {
System.out.println(array1[i]); //输出三个字符串
}

String str3 = "XXX.YYY.ZZZ";
String[] array3 = str3.split("\\.");
System.out.println(array3.length); //3
for (int i = 0; i < array3.length; i++) {
System.out.println(array3[i]); //输出三个字符串
}

String的格式化

public static String format(String format,Object… args):使用指定的格式字符串和参数返回一个格式化字符串

1
2
3
4
5
6
7
8
9
10
String str=null;

str=String.format("Hi,%s", "王力");
System.out.println(str); //Hi,王力

str=String.format("Hi,%s:%s.%s", "王南","王力","王张");
System.out.println(str); //Hi,王南:王力.王张

System.out.printf("字母a的大写是:%c %n", 'A');
System.out.println(str); //字母a的大写是:A

保留2位小数

1
2
3
double number = 123.456789;
String formatted = String.format("%.2f", number);
System.out.println(formatted); // 输出 123.46

基本类型与String之间的转换

基本类型转换为String

基本类型转换String总共有三种方式

  • **基本类型的值+ “ “**,最简单的方法
  • 包装类的静态方法toString(参数),static String toString(int i) :返回一个表示指定整数的String对象
  • String类的静态方法valueOf(参数),static String valueOf(int/boolean/double/float/long ) : 返回参数的字符串形式
1
2
3
4
5
6
7
8
9
10
11
12
//拼接法
int i1 = 100;
String s1 = i1+""; //s1="100"
System.out.println(s1+200);//100200

//包装类的toString
String s2 = Integer.toString(100);
System.out.println(s2+200);//100200

//String类的valueOf
String s3 = String.valueOf(100);
System.out.println(s3+200);//100200

String转换成对应的基本类型

包装类的parseXxx方法

所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型

public static char parseCharacter(String s) 将字符串参数转换为对应的char基本类型。

**public static byte parseByte(String s)**:将字符串参数转换为对应的byte基本类型。

**public static short parseShort(String s)**:将字符串参数转换为对应的short基本类型。

**public static int parseInt(String s)**:将字符串参数转换为对应的int基本类型。

**public static long parseLong(String s)**:将字符串参数转换为对应的long基本类型。

**public static float parseFloat(String s)**:将字符串参数转换为对应的float基本类型。

**public static double parseDouble(String s)**:将字符串参数转换为对应的double基本类型。

**public static boolean parseBoolean(String s)**:将字符串参数转换为对应的boolean基本类型。

1
2
3
4
5
6
String s1 = "100";
int i = Integer.parseInt(s1);
System.out.println(i-10); //90

int a = Integer.parseInt("a");//NumberFormatException,a不是数
System.out.println(a);

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常。

包装类的valueOf方法

public static Integer valueOf(String s):返回保存指定的 String 的值的 Integer 对象。

public static Double valueOf(String s):返回保存用参数字符串 s 表示的 double 值的 Double 对象。

public static Boolean valueOf(String s):返回一个用指定的字符串表示值的 Boolean 值。

public static Short valueOf(String s):返回一个保持指定 String 所给出的值的 Short 对象。

1
2
3
String s2 = "100"
int i = Integer.valueOf(s2);
System.out.println(i); //100

String
http://example.com/2023/05/13/Java/Java常用类/String/
作者
PALE13
发布于
2023年5月13日
许可协议