返回
Featured image of post String

String

介绍Java中的字符串类

目录

不可变之字符序列String

String的特性

  • java.lang.String 类代表字符串。Java 程序中所有的字符串文字(例如 "hello" )都可以看作是此类的实例。

  • 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改

  • 字符串 String 类型本身是 final 声明的,意味着我们不能继承 String

  • String 对象的字符内容是存储在一个字符数组 value[] 中的。"abc" 等效于char[] value = {‘h’,’e’,’l’,’l’,‘o’}

1
2
3
4
5
6
7
8
9
// jdk8中的部分String源码:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[]; // String 对象的字符内容存储在此数组中
 
    /** Cache the hash code for the string */
    private int hash; // Default to 0
}
  • private 意味着外面无法直接获取字符数组,而且 String 没有提供 value 的 get 和 set 方法。
  • final 意味着字符数组的引用不可改变,而且 String 也没有提供方法来修改 value 数组某个元素值。
  • 上述两个条件保证了 String 对象一旦被创建内容就是不可变的。一旦对字符串进行修改,就会产生新对象
  • Java 语言提供字符串串联符号 + ,以及将其他类型对象转换为字符串的特殊支持即 toString() 方法。

在 JDK9 中,value 的类型从 char[] 变成了 byte[]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { 
    @Stable
    private final byte[] value;
}

// 官方说明:
// ... that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

// 细节:
// ... The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.

String的内存结构

直接通过字面量赋值:

通过 new 创建字符串:

如图所示String str = new String(“Hello”)可能同时会创建两个String对象,一个在堆中,一个在字符串常量池中(如果程序之前从未使用过或很久没有使用过"Hello")。

字符串常量池的位置:

  • Jdk1.6及之前: 有永久代,运行时常量池在永久代运行时常量池包含字符串常量池
  • **Jdk1.7:**有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里
  • Jdk1.8及之后: 无永久代,运行时常量池在元空间字符串常量池里依然在堆里

如果想了解相关内容可以参考:java常量池详解Java 常量池详解(一)字符串常量池

判断字符串对象是在堆中还是字符串常量池中:

  1. 常量 + 常量:对象在常量池中,且常量池中不会存在相同内容的常量。这里的常量既包括了字面量,也包括了用 final 修饰的常量,后文的常量中也是这个意思。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 形式1 字面量 + 字面量
"Hello" + "World"; // 常量池

// 形式2 常量 + 常量
String prefix = "Hello";
String suffix = "World";
prefix + suffix; // 常量池

// 形式3 字面量 + 常量 / 常量 + 字面量
"Hello" + suffix; // 常量池
prefix + "World"; // 常量池
  1. 调用intern() 方法得到的对象:在常量池中
1
2
3
String str = new String("string"); // 堆
str.intern(); // 常量池
"string".intern(); // 常量池
  1. 常量 + 变量 / 变量 + 常量:对象在堆中

  2. concat() 方法拼接而成的字符串对象:在堆中。即使是两个常量相拼接也是在堆中,这与 concat() 的实现有关。

1
2
3
4
5
6
7
public String concat(String str) {
    if (str.isEmpty()) {
        return this;
    }
    // simpleConcat 底层就是在做拼接 然后将拼接的结果通过 new 返回
    return StringConcatHelper.simpleConcat(this, str);
}

String 的常用 API

构造器

API 描述
public String() 初始化新创建的 String 对象,使其表示空字符序列。
String(String original) 初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
public String(char[] value) 通过当前参数中的字符数组来构造新的 String。
public String(char[] value,int offset, int count) 通过字符数组的一部分来构造新的 String。
public String(byte[] bytes) 通过使用平台的默认字符集解码当前参数中的字节数组来构造新的 String。
public String(byte[] bytes,String charsetName) 通过使用指定的字符集解码当前参数中的字节数组来构造新的 String。指定的字符集只用于解码过程,Java 中所有的字符在内存中都以 Unicode 编码保存,上同。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 字面量定义方式:字符串常量对象
String str = "hello";

// 构造器定义方式:无参构造
String str1 = new String();

// 构造器定义方式:创建"hello"字符串常量的副本
String str2 = new String("hello");

// 构造器定义方式:通过字符数组构造
char chars[] = {'a', 'b', 'c','d','e'};     
String str3 = new String(chars);
String str4 = new String(chars, 0, 3);

// 构造器定义方式:通过字节数组构造
byte bytes[] = {97, 98, 99};     
String str5 = new String(bytes);
String str6 = new String(bytes,"GBK"); // 以 GBK 编码解码 以 Unicode 编码保存

String与其他结构间的转换

字符串 -> 基本数据类型、包装类:

  • Integer 包装类的 public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
  • 类似地,使用 java.lang 包中的 Byte、Short、Long、Float、Double 类调相应的 parseXxx 方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。

基本数据类型、包装类 -> 字符串:

  • 调用 String 类的 public String valueOf(int n) 可将 int 型转换为字符串。
  • 相应的 valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b) 可由参数的相应类型到字符串的转换。
  • 也可以通过将数据与空字符串拼接的方式将对应数据转为字符串类型。

字符数组 -> 字符串:

  • String 类的构造器:String(char[] value)String(char[] value, int offset, int length) 分别用字符数组中的全部字符和部分字符创建字符串对象。

字符串 -> 字符数组:

  • public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中并返回该字符数组。

  • public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到字符数组中的方法,这个字符数组需要使用者来提供

字符串 -> 字节数组(编码):

  • public byte[] getBytes() :使用平台的默认字符集将对应 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
  • public byte[] getBytes(String charsetName) :使用指定的字符集将对应 String 编码为 byte 序列,并将结果存储到新的 byte 数组。

字节数组 -> 字符串(解码):

  • String(byte[] bytes):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
  • String(byte[] bytes,int offset,int length) :用指定的字节数组的一部分,即从数组起始位置 offset 开始取 length 个字节使用默认字符集构造一个字符串对象。
  • String(byte[] bytes, String charsetName)String(byte[] bytes, int offset, int length ,String charsetName):按照指定的编码方式进行解码。

不乱码的两个条件:

  • 保证编码和解码的字符集是相同的
  • 解码时对应的字节序列是完整的,没有缺少字节
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Test
public void test0() throws Exception {
    String str = "中国";
    System.out.println(str.getBytes("ISO8859-1").length); // 2
    // ISO8859-1把所有的字符都当做一个byte处理,处理不了多个字节
    System.out.println(str.getBytes("GBK").length); // 4 每一个中文都是对应2个字节
    System.out.println(str.getBytes("UTF-8").length); // 6 常规的中文都是3个字节
    /*
     * 不乱码:(1)保证编码与解码的字符集名称一样(2)不缺字节
     */
    System.out.println(new String(str.getBytes("ISO8859-1"), "ISO8859-1")); // 乱码
    System.out.println(new String(str.getBytes("GBK"), "GBK")); // 中国
    System.out.println(new String(str.getBytes("UTF-8"), "UTF-8")); // 中国
}

常用方法

方法 描述
boolean isEmpty() 字符串是否为空字符串,不是判断是否为null
int length() 返回字符串的长度
String concat(String other) 拼接两个字符串,返回一个新的字符串,原来的字符串保持不变
boolean equals(Object obj) 比较字符串是否相等,区分大小写
boolean equalsIgnoreCase(Object obj) 比较字符串是否相等,不区分大小写
int compareTo(String other) 按照 Unicode 编码值比较字符串大小,区分大小写
int compareToIgnoreCase(String other) 按照 Unicode 编码值比较字符串大小,不区分大小写
String toLowerCase() 将字符串中大写字母转为小写
String toUpperCase() 将字符串中小写字母转为大写
String trim() 去掉字符串前后空白符
public String intern() 返回与当前字符串内容相同的,位于字符串常量池中的字符串的引用
boolean contains(String str) 判断字符串中是否包含 str
int indexOf(String str) 从前往后找当前字符串中 str,如果有则返回 str 第一次出现的下标,没有返回则返回 -1
int indexOf(String str, int fromIndex) 从指定索引开始,从前往后找当前字符串中 str,如果有则返回 str 第一次出现的下标,没有返回则返回 -1
int lastIndexOf(String str) 从后往前找当前字符串中 str,如果有则返回 str 第一次出现的下标,没有返回则返回 -1
int lastIndexOf(String str, int fromIndex) 从指定索引开始,从后往前找当前字符串中 str,如果有则返回 str 第一次出现的下标,没有返回则返回 -1
String substring(int beginIndex) 返回一个新的字符串,它是此字符串从 beginIndex 开始截取到最后的一个字符的子字符串
String substring(int beginIndex, int endIndex) 返回一个新的字符串,它是此字符串从 beginIndex 开始截取到 endIndex (不包含)的子字符串。
char charAt(index) 返回字符串 index 位置的字符
char[] toCharArray() 将字符串转换为一个新的字符数组返回
static String valueOf(char[] data) 返回一个新字符串,该字符串用整个 data 数组构建
static String valueOf(char[] data, int offset, int count) 返回一个新字符串,该字符串从 data 数组的 offset 索引开始,使用 count 个字符构建字符串
static String copyValueOf(char[] data) 返回一个新字符串,该字符串用整个 data 数组构建
static String copyValueOf(char[] data, int offset, int count) 返回一个新字符串,该字符串从 data 数组的 offset 索引开始,使用 count 个字符构建字符串
boolean startsWith(String str) 判断字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset) 判断字符串从指定索引开始的子字符串是否以指定前缀开始
boolean endsWith(String str) 判断此字符串是否以指定的后缀结束
String replace(char oldChar, char newChar) 返回一个新的字符串,它是通过用 newChar 替换字符串中出现的所有 oldChar 得到的,不支持正则表达式
String replace(CharSequence target, CharSequence replacement) 使用指定的字面值序列替换字符串所有匹配字面值目标序列的子字符串
String replaceAll(String regex, String replacement) 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串
String replaceFirst(String regex, String replacement) 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串

可变之字符序列 StringBuffer、StringBuilder

因为 String 对象是不可变对象,虽然可以共享常量池对象,但是对于频繁的字符串的修改和拼接操作来说,String 的效率极低、空间消耗较高。因此,JDK 又在 java.lang 包提供了可变字符序列 StringBufferStringBuilder 类型。

java.lang.StringBuffer 代表可变的字符序列,于JDK1.0中声明,可以对字符串内容进行增删查改操作但不会产生新的对象,而是在原来的字符串中进行操作。比如:

1
2
3
4
5
6
// 情况1:
String s = new String("我喜欢学习");

// 情况2:
StringBuffer buffer = new StringBuffer("我喜欢学习");
buffer.append("除了数学"); 

StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样。

区分String、StringBuffer、StringBuilder

  • String:不可变的字符序列;底层使用 char[] 数组存储数据(JDK8.0中)
  • StringBuffer:可变的字符序列;线程安全(方法有 synchronized 修饰),效率较低;底层使用 char[] 数组存储数据(JDK8.0中)
  • StringBuilder:可变的字符序列; JDK1.5 引入,线程不安全,效率高;底层使用 char[] 数组存储数据(JDK8.0中)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// String、StringBuffer、StringBuilder 的效率测试
@Test
public void test() {
    // 初始设置
    final int CIRCLE_SIZE = 100_000; // 循环十万次
    long startTime = 0L;
    long endTime = 0L;
    String text = "";
    StringBuffer buffer = new StringBuffer("");
    StringBuilder builder = new StringBuilder("");
    // 开始对比
    startTime = System.currentTimeMillis();
    for (int i = 0; i < CIRCLE_SIZE; i++) {
        buffer.append(String.valueOf(i));
    }
    endTime = System.currentTimeMillis();
    System.out.println("StringBuffer的执行时间:" + (endTime - startTime) + "ms");
    startTime = System.currentTimeMillis();
    for (int i = 0; i < CIRCLE_SIZE; i++) {
        builder.append(String.valueOf(i));
    }
    endTime = System.currentTimeMillis();
    System.out.println("StringBuilder的执行时间:" + (endTime - startTime) + "ms");
    startTime = System.currentTimeMillis();
    for (int i = 0; i < CIRCLE_SIZE; i++) {
        text = text + i;
    }
    endTime = System.currentTimeMillis();
    System.out.println("String的执行时间:" + (endTime - startTime) + "ms");
}

1
2
3
4
// 输出
StringBuffer的执行时间13ms
StringBuilder的执行时间5ms
String的执行时间2917ms

就执行效率而言:

StringBuilder > StringBuffer > String

常用 API

StringBuilder、StringBuffer 的 API 是完全一致的,并且很多方法与 String 相同。

API 描述
StringBuffer append(xxx) 提供了很多的 append() 方法,用于进行字符串追加的方式拼接
StringBuffer delete(int start, int end) 删除 [start,end) 之间字符
StringBuffer deleteCharAt(int index) 删除 [index] 位置字符
StringBuffer replace(int start, int end, String str) 替换 [start,end) 范围的字符序列为 str
void setCharAt(int index, char c) 替换 [index] 位置字符为 c
char charAt(int index) 返回 [index] 位置上的字符
StringBuffer insert(int index, xx) 在 [index] 位置插入 xx
int length() 返回存储的字符数据的长度
StringBuffer reverse() 反转字符序列
int indexOf(String str) 在当前字符序列中查询 str 的第一次出现下标
int indexOf(String str, int fromIndex) 在当前字符序列 [fromIndex, 最后] 中查询 str 的第一次出现下标
int lastIndexOf(String str) 在当前字符序列中查询 str 的最后一次出现下标
int lastIndexOf(String str, int fromIndex) 在当前字符序列 [fromIndex, 最后] 中查询 str 的最后一次出现下标
String substring(int start) 截取当前字符序列 [start,最后]
String substring(int start, int end) 截取当前字符序列 [start,end)
String toString() 返回此序列中数据的字符串表示形式
void setLength(int newLength) 设置当前字符序列长度为 newLength
Licensed under CC BY-NC-SA 4.0
鹅掌草の森已经茁壮生长了
发表了8篇文章 · 总计50.10k字 · 共 0 次浏览
记录任何我想记录的事情。若无特殊说明,则本博客文章均为原创,复制转载请保留出处。
使用 Hugo 构建
主题 StackJimmy 设计