1.介绍
String 是字符串类,位于 java.lang 包。底层是一个字符数组(char[]),并且被 final 修饰,不可被修改。
说明:这里讨论的是 JDK1.8 版本的 String,JDK1.9 中已改为 byte 数组
原因:字符数组的存储类型是 char,需要2个字节。但是,大多数字符串对象只包含 Latin-1 字符,这些字符只需要1个字节。因此通过 byte 数组存储,可以节省内存空间。由于如 UTF-16 等编码方式需要两个字节来存储字符,因此在 String 中引入了编码标志,来指明使用的是哪种编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// 保存字符串中的字符
private final char value[];
// 保存字符串 hash
private int hash;
}
2.初始化
2.1 使用
// 默认方式
String str_01 = "ab";
// char 方式
String str_02 = new String(new char[]{'a', 'b'});
String str_022 = new String(new char[]{'a', 'b','c'},0,2);
// int 方式
String str_03 = new String(new int[]{0x61, 0x62, 0x63}, 0, 2);
// byte 方式
String str_04 = new String(new byte[]{0x61, 0x62});
// 以上的结果都为 "ab"
2.2 源码简述
// 无参构造函数
public String() {
this.value = "".value;
}
// 参数为字符串,默认方式
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
// 参数为字符数组,char 方式
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
// 参数为整数数组,int 方式
public String(int[] codePoints, int offset, int count) {
// 判断 offset 合法性,即数组中开始的位置
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
// 判断 count 合法性,即截取的元素个数
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// 当 offset+count > codePoints.length 时,抛出异常
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: 判断 int 所表示的 char 是否合法,并计算 [offset,end) 范围内的 int 需要用多少个 char 表示
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
// 判断是否在 Basic Multilingual Plane(BMP)中(Unicode中的一个编码区段),该区间内只需一个 char 就可表示
if (Character.isBmpCodePoint(c))
continue;
// 用于判断当前输入是否是一个标准的codepoint,即是否位于 0x000000~0X10FFFF 之间;剩下区间内的 int 需要两个 char 表示
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2: 实例化 char 数组,并填充
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
// Character$toSurrogates
static void toSurrogates(int codePoint, char[] dst, int index) {
// We write elements "backwards" to guarantee all-or-nothing
dst[index+1] = lowSurrogate(codePoint);
dst[index] = highSurrogate(codePoint);
}
// 参是是 byte 数组,byte 方式
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
2.3 不可变性(final)
字符串 String 的不可变性由 private 和 final 共同决定。
final使得数组引用一旦被赋值就不可更改,说明该数组引用只能指向最初赋值的内存区域;但是通过引用还是可以修改其所指向的内存区域的内容-
final char[] value = new char[]{'a','b'}; System.out.println(Arrays.toString(value)); // 输出结果:[a, b] // 编译错误:无法将值赋给 final 变量 'value' // value = new char[10]; value[0] = 'y'; System.out.println(Arrays.toString(value)); // 输出结果:[y, b]
-
private将数组引用封装在类内部,导致外部对象无法对数组引用直接进行操纵,使得外部对象无法通过数组引用直接修改所指向内存区域的内容
经典面试题:
String str_01 = "abc";
String str_02 = "abc" + "def";
String str_03 = str_01 + "def";
// 一共会创建 3 个 String 对象
3.intern()
intern() 方法返回字符串对象的规范化表示形式。当调用 intern() 方法时,首先检查字符串池中是否存在与之通过 equals() 方法判断后相同的字符串,存在则直接返回字符串池中该字符串的引用;否则根据 JDK 版本进行不同的操作。
在 JDK6 和 JDK7、8 中表现形式不同:
- JDK6
- 如果字符串池中存在该字符串,则直接返回字符串池中的对象地址
- 如果字符串池中不存在该字符串,则把对象复制一份(new),并返回该对象的地址
- JDK7 & JDK8
- 如果字符串池中存在该字符串,则直接返回字符串池中的对象地址
- 如果字符串池中不存在该字符串,则会把对象的引用地址复制一份,放入字符串池,并返回字符串池中的引用地址
总结:
- 采用 new 创建的字符串对象不进入字符串池;默认方式创建的字符串加入字符串池
- 字符串相加时,都是静态字符串时,结果加入字符串池;否则不加入字符串池
例子:
String str_1 = "a";
String str_2 = "b";
String str_3 = "ab";
String str_4 = "a" + "b";
String str_5 = str_1 + "b";
String str_6 = str_1 + str_2;
String str_7 = new String("ab");
String str_8 = new String("ab");
// new 创建的字符串不保存在字符串池
System.out.println(str_7 == str_8);
System.out.println(str_8 == str_3);
// intern() 得到的是字符串池中的引用;默认方式创建的字符串被保存进字符串池
System.out.println(str_7.intern() == str_8.intern());
System.out.println(str_8.intern() == str_3);
// 两个常量通过 + 拼接得到的字符串被保存进字符串池;如果存在变量,则不保存进字符串池
System.out.println(str_3 == str_4);
System.out.println(str_3 == str_5);
System.out.println(str_3 == str_6);
System.out.println(str_5 == str_6);
System.out.println(str_5.intern() == str_6.intern());
// 运行结果:
// false
// false
// true
// true
// true
// false
// false
// false
// true
String str_1 = new String("ab");
String str_2 = new String("ab");
String str_3 = "ab";
上面代码实例化过程创建的对象:
- 可知在创建
str_1时创建了两个String引用,一个驻留在字符串常量池
String str_4 = "ef";
String str_5 = new String("ef");
String str_6 = new String("ef");
上面代码实例化过程创建的对象:
- 可知在
str_4时创建了一个String引用,驻留在字符串常量池
4.常用API
4.1 concat() 连接
concat() 方法是字符串连接方法,用来代替 号。
String s = new String("ab");
s = s.concat("c");
// 结果是:abc
4.2 length() 获取长度
length() 方法是获取字符串长度。底层是直接获取字符数组的长度。
String s = new String("ab");
int len = s.length();
// 结果是:2
4.3 isEmpty() 判空
isEmpty() 方法是判断字符串是否为空,底层是通过判断字符数组长度是否为 0。字符串为空,则返回 ,否则为 。
String s = new String("ab");
boolean b = s.isEmpty()
// 结果是:false
String s2 = new String();
boolean b2 = s2.isEmpty()
// 结果是:true
4.4 charAt() 获取指定位置元素
charAt() 方法是根据入参索引,获取字符串指定位置的字符。索引位置从 0 开始,从左到右依次递增。如果入参索引超过字符串的索引位置,会抛出 java.lang.StringIndexOutOfBoundsException 异常。
String s = new String("ab");
char c = s.charAt(0)
// c = 'a'
char c2 = s.charAt(1)
// c2 = 'b'
4.5 codePointAt() 获取指定位置 ASCII 值
codePointAt() 方法是根据入参索引,获取指定位置的元素,并返回其 ASCII 值。如果入参索引超过字符串的索引位置,会抛出 java.lang.StringIndexOutOfBoundsException 异常。
String s = new String("ab");
int i = s.codePointAt(0);
// i = 97
int j = s.codePointAt(1);
// j = 98
4.6 getBytes() 获取字节数组
getBytes() 方法是将字符串转化为字节数组的方法。
String s = new String("ab");
byte[] bytes = s.getBytes();
// 结果为:[97, 98]
4.7 equals() 比较
equals() 方法是判断两个字符串是否相等。相等则返回 ,否则为 。
String s = new String("ab");
String s2 = new String("ab");
String s3 = new String("ac");
boolean b = s.equals(s2);
// 结果为:true
boolean b2 = s.equals(s3);
// 结果为:false
4.8 equalsIgnoreCase() 忽略大小写比较
equalsIgnoreCase() 方法比较两个字符串是否相等,忽略其大小写。
String s = new String("ab");
String s2 = new String("Ab");
String s3 = new String("ac");
s.equalsIgnoreCase(s2);
// 结果为:true
s.equalsIgnoreCase(s3);
// 结果为:false
4.9 startsWith() 前缀判断
startsWith() 方法是判断字符串是否以某个前缀开头。
String s = new String("abedfgh");
boolean b = s.startsWith("ab");
// 结果为:true
boolean b2 = s.startsWith("abf");
// 结果为:false
4.10 endsWith() 后缀判断
endsWith() 方法是判断字符串是否以某个后缀结尾。
String s = new String("abedfgh");
boolean b = s.endsWith("gh");
// 结果为:true
boolean b2 = s.endsWith("bgh");
// 结果为:false
4.11 indexOf() 判断元素位置
indexOf() 方法是判断元素在字符串中的索引位置。如果字符串中有多个相同元素,则返回从左到右遇到的第一个相同的元素的位置。如果字符串中不存在该元素,则返回 。
String s = new String("abedfghab");
int posA = s.indexOf('a');
// posA = 0
int posH = s.indexOf('h');
// posH = 6
int posZ = s.indexOf('z');
// posZ = -1
int pos = s.indexOf("ab");
// pos = 0
int pos2 = s.indexOf("ab", 2);
// pos2 = 7
4.12 lastIndexOf() 判断元素结尾位置
lastIndexOf() 方法返回某个元素在字符串中最后出现的位置。
String s = new String("abedfghab");
int posA = s.lastIndexOf('a');
// posA = 7
int posH = s.lastIndexOf('h');
// posH = 6
int posE = s.lastIndexOf('e', 1);
// posE = -1
int pos = s.lastIndexOf("ab");
// pos = 7
4.13 substring() 截取字符串
substring() 方法是根据索引范围截取字符串。入参 和 ,要在字符串的索引范围内,否则抛出 java.lang.StringIndexOutOfBoundsException 异常。
String s = new String("abc");
String sub = s.substring(0, 2);
// sub = "ab"
String sub2 = s.substring(0, 3);
// sub2 = "abc"
String sub3 = s.substring(1);
// sub3 = "bc"
4.14 split() 拆分
split() 方法是根据入参字符拆分字符串,返回一个字符串数组。
String s = new String("a-b-c-d-e");
String[] str = s.split("-");
// str = [a, b, c, d, e]
String[] str2 = s.split("-", 2);
// str2 = [a, b-c-d-e]
String[] str3 = s.split(",");
// str3 = [a-b-c-d-e]
4.15 replace() 替换
replace() 方法是替换字符串中的相应元素。
| 方法 | 描述 |
|---|---|
| public String replace(char oldChar, char newChar) | 将字符串中所有 oldChar 元素替换为 newChar 元素 |
| public String replace(CharSequence target, CharSequence replacement) | 将字符串中字符串序列进行替换 |
| public String replaceAll(String regex, String replacement) | 基于规则表达式进行替换 |
String s = new String("a1-b2-c-d-e");
String rep = s.replace('-', '.');
// rep = "a1.b2.c.d.e"
String rep2 = s.replace('.', 'o');
// rep2 = "a1-b2-c-d-e"
String rep3 = s.replaceAll("\d", "*");
// rep3 = "a*-b*-c-d-e"
4.16 toUpperCase() 转大写
toUpperCase() 方法是将字符串中的小写字母转为大写。
String s = new String("a1-b2-c-d-e");
String str = s.toUpperCase();
// str = "A1-B2-C-D-E"
4.17 toLowerCase() 转小写
toLowerCase() 方法是将字符串中的大写字母转为小写。
String s = new String("A1-B2-c-d-e");
String str = s.toLowerCase();
// str = "a1-b2-c-d-e"
4.18 toCharArray() 转数组
toCharArray() 方法是将字符串转为字符数组。
String s = new String("ab");
char[] chars = s.toCharArray();
// chars = ['a', 'b']
4.19 String.format() 创建格式化字符串
String.format() 方法用于创建格式化的字符串以及连接多个字符串对象。
| 转换符 | 描述 | 转换符 | 描述 |
|---|---|---|---|
| %s | 字符串类型 | %c | 字符类型 |
| %d | 十进制整数 | %x | 十六进制整数 |
| %o | 八进制整数 | %b | Boolean 类型 |
| %f | 浮点数 | %a | 十六进制浮点数 |
| %e | 指数形式 | %h | 散列码 |
| %% | 百分号 | %n | 换行 |
String format = String.format("%s\t%c\t%d\t%x\t%f", "st", 'b', 2, 0xFF, 2.6);
// format = "st b 2 ff 2.600000"
4.20 String.valueOf() 转字符串
String.valueOf() 方法是将入参转为字符串。
String str = String.valueOf(22);
// str = "22"
String str2 = String.valueOf(2.2);
// str2 = "2.2"
String str3 = String.valueOf(new char[]{'a', 'b', 'c', 'd', 'e'}, 0, 2);
// str3 = "ab"
4.21 trim() 首尾去空格
trim() 方法是将字符串的开头位置和尾部位置的空格去掉。
String s = new String(" ab");
String s2 = new String(" abc ");
String str = s.trim();
// str = "ab"
String str2 = s2.trim();
// str2 = "abc"
4.22 hashCode() 获取哈希值
hashCode() 方法是获取字符串的哈希值。
String s = new String("ab");
int hashCode = s.hashCode();
// hashCode = 3105