在Java语言中,字符串数据实际上由String类所实现的。Java字符串类分为两类:一类是在程序中不会被改变长度的不变字符串;另一类是在程序中会被改变长度的可变字符串。Java环境为了存储和维护这两类字符串提供了 String和StringBuffer两个类(在JDK1.5版本后出现了StringBuilder,该类被设计用作 StringBuffer 的一个简易替换)。

一、字符串(String)

1、字符串概念了解

String 类代表字符串,Java

程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。

字符串是常量,字符串对象表示固定长度的字符序列,它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String

对象是不可变的,所以可以共享。String类可能是Java程序中使用的最频繁的类了。(注意:String类是被final修饰的,不能被继承,String底层实现其实是字符数组。)

2、字符串对象的创建

在java中可以通过以下两种方式创建字符串。

1、使用字符串常量直接初始化一个String对象。

如:String str = "this is a string";

2、使用String的构造方法初始化字符串对象。

如:String str = new String("this is a string");

实际运用中,我们要避免第二种方式,第二种方式创建了两个String对象。首先,Java虚拟机创建了String对象“abc”。然后,Java虚拟机创建一个新的String对象,并把字符串“abc”传入构造函数。这是一次完全没有必要的构造,既影响了性能,也提高了代码阅读难度。

由于字符串是字符序列,所以可以嵌入特殊字符。

如:String str = "\t\n";

3、字符串的常见操作

1、字符(子串)查找

int indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引。(没有找到返回-1)
int lastIndexOf(int ch) :返回指定字符在此字符串中最后一次出现处的索引。(没有找到返回-1)
String str = "this is a string!";
System.out.println(str.indexOf('i')); // 输出结果为:2
System.out.println(str.indexOf('!')); // 输出结果为:16
System.out.println(str.indexOf('y')); // 输出结果为:-1
// 重载方法:int indexOf(String str)
// 返回指定子字符串在此字符串中第一次出现处的索引。
System.out.println(str.indexOf("str"));// 输出结果为:10
System.out.println(str.lastIndexOf('s')); // 输出结果为:10

2、字符串连接

“+” 字符串连接或者String concat(String str) :将指定字符串连接到此字符串的结尾。
// 常用
String str1 = "I love" + " you!";
System.out.println(str1); // 输出结果为:I love you!
// 少用
String str2 = "abc".concat("xyz");
System.out.println(str2);// 输出结果为:abcxyz

在Java中,字符串连接是一个非常常用的操作。将一个字符串和另一个字符串连接起来,从而生成第三个字符串。在实际编码中,我们也基本都是用(+)号作为字符串连接的捷径。+符号既可以操作数值的加减,也可以用于连接字符串,这叫做操作符的重载。

3、 索引值及字符串长度

char charAt(int index) :返回指定索引处的 char 值。
(参数index为负或者大于字符串长度则抛出该异常:java.lang.StringIndexOutOfBoundsException)
int length() :返回此字符串的长度。
String str = "this is a string!";
System.out.println("字符串的长度为:" + str.length()); // 输出结果为:字符串的长度为:17
System.out.println(str.charAt(5)); // 输出结果为:i

4、 字符串前后缀、判空、大小写转换即子串查询

boolean startsWith(String prefix):判断此字符串是否以指定的前缀开始。
boolean endsWith(String suffix):判断此字符串是否以指定的后缀结束。
boolean isEmpty() :当且仅当 length() 为 0 时返回 true。
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true。
String toUpperCase():使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
String toLowerCase():使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
String str = "This is a String!";
System.out.println(str.startsWith("Th")); // 输出结果为:true
System.out.println(str.startsWith("a"));// 输出结果为:false
System.out.println(str.endsWith("ing!"));// 输出结果为:true
System.out.println(str.endsWith("ing"));// 输出结果为:false
System.out.println(str.toUpperCase());// 输出结果为:THIS IS A STRING!
System.out.println(str.toLowerCase());// 输出结果为:this is a string!
System.out.println(str.contains("is"));// 输出结果为:true
System.out.println(str.isEmpty());// 输出结果为:false
str = "";
System.out.println(str.isEmpty());// 输出结果为:true

5、字符串与基本数据类型转换

static String valueOf(int i)  返回 int 参数的字符串表示形式。(其他类型同理)
String x = String.valueOf(12);// 基本数据类型转字符串
System.out.println(x);
int a = 5; // 基本数据类型转字符串
Integer i = new Integer(a);
i.toString();
// 顺便提下
int y = Integer.parseInt("21"); // 字符串转基本数据类型
System.out.println(y);

6、字符串转字符数组

char[] toCharArray() :将此字符串转换为一个新的字符数组。
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
/*
* 从控制台输入字符串,计算出该字符串中
* 大写字母出现的个数
* 小写字母出现的个数
* 数字出现的个数
* 特殊字符出现的个数
*/
Scanner scan = new Scanner(System.in);
System.out.println("请输入字符串:");
String str = scan.nextLine();
int num1, num2, num3, num4;
num1 = num2 = num3 = num4 = 0;
char[] strs = str.toCharArray();// 将此字符串转换为一个新的字符数组。
for (char s : strs) {
if (s >= 'A' && s <= 'Z') {
num1++;
} else if (s >= 'a' && s <= 'z') {
num2++;
} else if (s >= '0' && s <= '9') {
num3++;
} else {
num4++;
}
}
System.out.println("大写字母:" + num1);
System.out.println("小写字母:" + num2);
System.out.println("数字:" + num3);
System.out.println("特殊字符:" + num4);
}
}

7、字符串去空与替换

String trim() :返回一个新字符串,它去除了原字符串首尾的空格。
String replace(char oldChar, char newChar)  返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String str = " this is a string! ";
System.out.println(str.trim());// 运行结果为:this is a string!
System.out.println(str.replace("is", "xyz"));// 运行结果为: thxyz xyz a string!

8、 字符串分割与截取

String[] split(String regex) :根据给定正则表达式的匹配拆分此字符串。
String substring(int beginIndex) :返回一个新的字符串,它包含此字符串中索引为beginIndex后的所有字符
String substring(int beginIndex, int endIndex) :返回一个新字符串,它包含此字符串中从索引beginIndex到endIndex之间的所有字符
String str = "乒乓球-篮球-排球";
System.out.println("从第5个字符截取到末尾的结果:" + str.substring(4));
System.out.println("从第5个字符截取到第6个字符的结果:" + str.substring(4, 6));
// 注意包含头,不包含尾
System.out.print("分割后字符串数组中的元素依次是:");
String[] strArray = str.split("-");
for (int i = 0; i 
if (i != strArray.length - 1) {
System.out.print(strArray[i] + " ");// 如果不是数组的最后一个元素,在元素后面加空格。
} else {
System.out.println(strArray[i]);// 数组的最后一个元素后面不加逗号
}
}

运行结果为:

从第5个字符截取到末尾的结果:篮球-排球

从第5个字符截取到第6个字符的结果:篮球

分割后字符串数组中的元素依次是:乒乓球 篮球 排球

9、字符串相等判断

boolean equals(Object anObject) :将此字符串与指定的对象比较。(判断字符串是否相等建议使用equals方法。)
String str1 = new String("xyz");
String str2 = new String("xyz");
System.out.println(str1 == str2);// 输出结果为:false
System.out.println(str1.equals(str2)); // 输出结果为:true
// 1、str1和str2分别指向不同的对象,那么str1 == str2将返回false
// 用equals方法,该方法比较的是字符串的内容是否相同。
String str3 = "xyz";
String str4 = "xyz";
System.out.println(str3 == str4);// 输出结果为:true
System.out.println(str3.equals(str4)); // 输出结果为:true
// 2、用"=="运算符,该运算符表示指向字符串的引用是否相同,
// 上面: String str3="xyz";String str4 = "xyz",那么str3 == str4将返回true。
// 这是因为在java中字符串的值是不可改变的,相同的字符串在内存中只会存一份,
// 所以a和b指向的是同一个对象;

二、StringBuffer

1、StringBuffer的了解

由于字符串是常量,因此一旦创建,其内容和长度是不可改变的。如果需要对一个字符串进行修改,则只能创建新的字符串。为了方便对字符串进行修改,在JDK中提供了StringBuffer类(也称字符串缓冲区)。StringBuffer类和String类最大的区别在于它的内容和长度是可以改变的。StringBuffer类似一个字符容器,当在其中添加或删除字符时,并不会产生新的StringBuffer对象。

2、StringBuffer的常用方法(操作)

StringBuffer append(char c):将 char 参数的字符串表示形式追加到此序列。
StringBuffer insert(int offset, String str) :将字符串str插入offset位置
StringBuffer deleteCharAt(int index):移除此序列指定位置的字符
StringBuffer delete(int start,int end):删除StringBuffer对象指定范围内的字符或字符串序列
StringBuffer replace(int start,int end,String str):在StringBuffer对象中替换指定的字符或字符串序列。
void setCharAt(int index,char ch):修改指定位置的index处的字符序列
String toString():返回StringBuffer缓冲区中的字符串
StringBuffer reverse():将此字符序列用其反转形式取代。
public class Demo {
public static void main(String[] args) {
System.out.println("1、添加------------");
add();
System.out.println("2、删除------------");
delete();
System.out.println("3、修改------------");
alter();
}
public static void add() {
StringBuffer sb = new StringBuffer();// 定义一个字符串缓冲区
sb.append("abcdefg");// 添加到StringBuffer中
System.out.println("append添加结果:" + sb);
sb.insert(2, "123");// 在指定位置插入字符串
System.out.println("insert插入结果:" + sb);
}
public static void delete() {
StringBuffer sb = new StringBuffer("abcdefg");// 定义一个字符串缓冲区
sb.delete(1, 5);// 包含头,不包含尾
System.out.println("删除指定范围的结果:" + sb);
sb.deleteCharAt(2);
System.out.println("删除指定位置的结果:" + sb);
sb.delete(0, sb.length());
System.out.println("清空缓冲区的结果是:" + sb);
}
public static void alter() {
StringBuffer sb = new StringBuffer("abcdefg");// 定义一个字符串缓冲区
sb.setCharAt(1, 'q');
System.out.println("修改指定位置字符的结果:" + sb);
sb.replace(1, 3, "xyz");// 包含头,不包含尾
System.out.println("替换指定位置字符串的结果:" + sb);
System.out.println("字符串反转后的结果:" + sb.reverse());
}
}

运行结果为:

1、添加------------

append添加结果:abcdefg

insert插入结果:ab123cdefg

2、删除------------

删除指定范围的结果:afg

删除指定位置的结果:af

清空缓冲区的结果是:

3、修改------------

修改指定位置字符的结果:aqcdefg

替换指定位置字符串的结果:axyzdefg

字符串反转后的结果:gfedzyxa

三、StringBuilder

1、StringBuilder的了解

JDK1.5开始,StringBuilder是一个可变的字符序列。此类提供一个与 StringBuffer 兼容的

API,但不保证同步。该类被设计用作 StringBuffer

的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比

StringBuffer 要快。

2、StringBuilder的常用方法

同StringBuffer,这里不再赘述。

3、String、StringBuffer和StringBuilder的区别

(1)、String类中使用字符数组保存字符串,使用“final”修饰符,一旦创建后,内容和长度都是无法改变的。

而StringBuilder和StringBuffer表示字符容器,其内容和长度都可以随时修改。在操作字符串时,如果该字符串仅用于表示数据类型,则使用String类即可,但是如果需要对字符串中的字符进行增删操作,则使用StringBuilder和StringBuffer类。使用

StringBuffer 类时,每次都会对 StringBuffer

对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

(2)、String类覆盖了Object类的equals()方法,而StringBuilder和StringBuffer没有覆盖Object类的equals()方法。

(3)、String是线程安全的,StringBuffer 是线程同步的,即线程安全的,通常用于多线程。StringBuilder 是线程不同步的,即线程不安全,通常用于单线程。

1 String

String:字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。

String类的包含如下定义:

/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;

用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。

2 StringBuffer(JDK1.0)

StringBuffer:字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。

Java.lang.StringBuffer线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。

StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。

3 StringBuilder(JDK5.0)

StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。

java.lang.StringBuilder是一个可变的字符序列,是JDK5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

其构造方法如下:

构造方法

描述

StringBuilder()

创建一个容量为16的StringBuilder对象(16个空元素)

StringBuilder(CharSequence cs)

创建一个包含cs的StringBuilder对象,末尾附加16个空元素

StringBuilder(int initCapacity)

创建一个容量为initCapacity的StringBuilder对象

StringBuilder(String s)

创建一个包含s的StringBuilder对象,末尾附加16个空元素

在大部分情况下,StringBuilder > StringBuffer。这主要是由于前者不需要考虑线程安全。

4 三者区别

String 类型和StringBuffer的主要性能区别:String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的

String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String

,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。

使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。

在某些特别情况下, String 对象的字符串拼接其实是被 JavaCompiler 编译成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,例如:

String s1 = “This is only a” + “ simple” + “ test”;

StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);

生成 String s1对象的速度并不比 StringBuffer慢。其实在Java Compiler里,自动做了如下转换:

Java Compiler直接把上述第一条语句编译为:

String s1 = “This is only a simple test”;

所以速度很快。但要注意的是,如果拼接的字符串来自另外的String对象的话,Java Compiler就不会自动转换了,速度也就没那么快了,例如:

String s2 = “This is only a”;

String s3 = “ simple”;

String s4 = “ test”;

String s1 = s2 + s3 + s4;

这时候,Java Compiler会规规矩矩的按照原来的方式去做,String的concatenation(即+)操作利用了StringBuilder(或StringBuffer)的append方法实现,此时,对于上述情况,若s2,s3,s4采用String定义,拼接时需要额外创建一个StringBuffer(或StringBuilder),之后将StringBuffer转换为String;若采用StringBuffer(或StringBuilder),则不需额外创建StringBuffer。

5 使用策略

(1)基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。

(2)不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则。例如:

String result = "";
for (String s : hugeArray) {
result = result + s;
}
// 使用StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
sb.append(s);
}
String result = sb.toString();

当出现上面的情况时,显然我们要采用第二种方法,因为第一种方法,每次循环都会创建一个String result用于保存结果,除此之外二者基本相同(对于jdk1.5及之后版本)。

(3)为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。

(4)StringBuilder一般使用在方法内部来完成类似"+"功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中。

(5)相同情况下使用 StringBuilder 相比使用

StringBuffer 仅能获得 10%~15%

左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在StringBuffer上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer。