java字符串
一、String类
概述:字符串是由多个字符组成的一串数据(字符序列),它可以看成是一个字符数组。Java程序中的所有字符串面值(如:“abc”)都可以看成是一个字符串对象。
String str = "abc";
相当于:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
// String底层是靠字符数组实现的。
一)特点
- String类被final修饰,不可被继承
- 字符串是不可变序列,是常量,一旦被赋值,就不能被改变。(其值不能改变,但是String类型的引用所指向的地址值可以发生改变)
- 字符串字面值存放在方法区中的字符串常量池,字符串常量池中一定不存在两个相同的字符串
public static void main(String[] args) {
String name = "zhangsan";
/**
* 重新赋值时,并不是直接在原地址上覆盖zhangsan,而是在字符串池中
* 找到(创建)lisi,然后把lisi的地址赋值给name,从而实现新的赋值过程
*/
name = "lisi";
}
注意:当我们重新赋值时,并不是直接修改他的字面值,因为字面值是不可改变的,而是通过重新开辟一个空间,把新空间的地址赋值给变量,这就是不可变性
二)构造方法
方法 | 描述 |
---|---|
String(); | 无参构造 |
String(byte[] bytes); | 把字节数组转换成字符串 |
String(byte[],int offset,int length); | 把字节数组的一部分转换成字符串 |
String(char[] value); | 把字符数组转换成字符串 |
String(char[] value,int offset,int count); | 把字符数组的一部分转换成字符串 |
String(String original); | 把字符串常量转换成字符串 |
示例:
// 直接创建
String s = "abc";// 创建一个字符串对象abc,放在方法区字符串常量池中
// 通过构造器传字符串的创建
String s1 = new String("abc");// 在堆中创建一个字符串对象,然后在常量池中找到并返回abc对象
// 通过字符数组构造
char chars[] = {'a', 'b', 'c'};
String str2 = new String(chars);// 在堆中创建一个字符串对象,然后在常量池中找到并返回abc对象
// 通过字节数组构造
byte bytes[] = { 97, 98, 99 };
String str3 = new String(bytes);// 在堆中创建一个字符串对象,然后在常量池中找到并返回abc对象
注意:字符串直接赋值的方式是先到方法区的字符串常量池里面查找,如果有就直接返回,如果没有就创建并返回。
String str = “abc”;
与String str2 = new String(“abc”);
的区别
- String str = “abc”:
可能创建一个或者不创建对象
。直接到方法区的字符串常量池里面查找,如果abc
在字符串池中存在, str直接指向这个内存地址;如果abc
在字符串池中不存在,会在java字符串池中创建一个String对象abc
,然后str指向这个内存地址,无论以后用这种方式创建多少个值为abc
的字符串对象,始终指向的都是该内存地址,即以""
方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一 个 String 对象,并在字符串池中维护。- String str = new String(“abc”):
至少会创建一个对象,也有可能创建两个
。因为用到new
关键字,肯定会在堆中创建一个String对象,如果字符池中已经存在abc
,则不会再字符串池中创建一个String对象,如果不存在,则会在字符串常量池中也创建一个对象。
三)常用方法
方法名 | 描述 |
---|---|
int length() | 返回字符串的长度 |
char charAt(int index) | 根据下标获取字符 |
int indexOf(String str) | 查找str首次出现的下标,存在,则返回该下标;不存在,则返回-1 |
int indexOf(int ch,int fromIndex); | 查找指定字符在指定位置后第一次出现处的索引 |
int indexOf(String str,int fromIndex); | 查找指定字符串在指定位置后第一次出现处的索引 |
char[] toCharArray() | 将字符串转换成数组。 |
byte[] getBytes(); | 把字符串转换为字节数组 |
static String valueOf(...); | String类的valueOf方法可以把任意类型的数据转换为字符串 |
String trim() | 去掉字符串前后的空格 |
String toLowerCase(); | 把字符串转换为小写 |
String toUpperCase() | 把字符串转换为大写 |
String replace(String oldString,String newString) | 把字符串中的子字符串替换为新的子字符串,原字符串不变 |
String[] split(String str) | 根据str做拆分,如空格符或给定正则表达式的匹配拆分此字符串 |
String concat(String str); | 把字符串进行拼接 |
String subString(int beginIndex,int endIndex) | 在字符串中截取出一个子字符串,范围[beginIndex,endIndex) ,包含开始,不含结束 |
boolean equals(Object obj); | 比较字符串的内容是否相同,区分大小写 |
boolean equalsIgnoreCase(String str); | 比较字符串的内容是否相同,忽略大小写 |
int compareTo(String str); | 按字典顺序比较两个字符串 |
boolean contains(String str) | 判断当前字符串中是否包含str |
boolean startsWith(String str); | 判断字符串是否以某个指定的字符串开头 |
boolean endsWith(String str) | 判断字符串是否以str结尾 |
boolean isEmpty(); | 判断字符串内容是否为空 |
native String intern(); | 直接指向常量池 |
new String()的一些参考:
演示:
System.out.println("---------字符串方法的使用 1-------------");
//字符串方法的使用
//1、length();返回字符串的长度
//2、charAt(int index);返回某个位置的字符
//3、contains(String str);判断是否包含某个子字符串
String content = "java是世界上最好的java编程语言,java真香";
System.out.println(content.length()); // 26
System.out.println(content.charAt(content.length() - 1)); // 香
System.out.println(content.contains("java")); // true
System.out.println(content.contains("php")); // false
System.out.println("--------字符串方法的使用 2--------------");
//字符串方法的使用
//4、toCharArray();返回字符串对应的数组
//5、indexOf();返回子字符串首次出现的位置
//6、lastIndexOf();返回字符串最后一次出现的位置
System.out.println(Arrays.toString(content.toCharArray()));
System.out.println(content.indexOf("java")); // 0
System.out.println(content.indexOf("java", 4)); // 11
System.out.println(content.lastIndexOf("java"));// 20
System.out.println("------------字符串方法的使用3------------------");
//7、trim();去掉字符串前后的空格
//8、toUpperCase();//把小写转成大写 toLowerCase();把大写转成小写
//9、endWith(str);判断是否已str结尾,startWith(str);判断是否已str开头
String content2 = " hello World ";
System.out.println(content2.trim()); // hello World
System.out.println(content2.toUpperCase()); // HELLO WORLD
System.out.println(content2.toLowerCase()); // hello world
String filename = "hello.java";
System.out.println(filename.endsWith(".java")); // true
System.out.println(filename.startsWith("hello")); // true
System.out.println("------------字符串方法的使用4------------------");
//10、replace(char old,char new); 用新的字符或字符串替换旧的字符或字符串
//11、split();对字符串进行拆分
//12、S.substring(开始,结束) 截取某段字符
System.out.println(content.replace("java", "php"));
String s = "java is the best, programing language";
System.out.println(s.substring(5,16)); // is the best
// 根据空格分隔字符串
String[] arr = s.split(" ");
for (String i : arr) {
System.out.println(i);
}
System.out.println("---------------");
// 根据空格和逗号分隔,中括号表示选择,可以选择空格或逗号分隔
String[] arr2 = s.split("[ ,]");
for (String i : arr2) {
System.out.println(i);
}
System.out.println("---------------");
/**
* 根据空格和逗号分隔,中括号表示选择,可以选择空格或逗号分隔,
* +号表示可以出现多个空格或逗号
*/
String[] arr3 = s.split("[ ,]+");
for (String i : arr3) {
System.out.println(i);
}
//补充两个方法equals 、compareTo();比较大小
System.out.println("---------补充---------");
String s1 = "hello";
String s2 = "HELLO";
System.out.println(s1.equalsIgnoreCase(s2));
String s3 = "abc";//97
String s4 = "ayzawe";//120
System.out.println(s3.compareTo(s4));
String s5 = "abc";
String s6 = "abc";
System.out.println(s5.compareTo(s6));
intern()
/**
* intern:先用equals方法判断字符串常量池中是否存在指定字符串,存在则直接返回常量
* 池中的字符串地址,如果字符串池中没有该字符串,则创建一个然后返回地址
* 即:无论过程多么复杂,intern方法最终返回的一定是字符串常量池中的字符串地址
*/
String s1 = "a";
String s2 = new String("a");
System.out.println(s1 == s2);// false
System.out.println(s1 == s2.intern());// true
System.out.println(s2 == s2.intern());// false
System.out.println(s1 == s1.intern());// true
注意内容为空与字符串为空的区别:
字符串内容为空:String str = "";
字符串对象为空:String str = null;(对象为空,无法调用方法:空指针异常)
String类拼接
重要规则:常量相加,发生在常量池中;变量相加,发生在堆中
如果两个字符串变量相加,先开辟空间,再做拼接。
- 如果
两个字符串常量相加,先做拼接
,然后在字符串常量池里面查找,如果有就直接返回,如果没有就创建并返回
String a = "hello";
String b = "World";
String s = "hello" + "World";//常量相加,直接在常量池中创建对象
String s1 = a + b; // 变量相加,要先在堆中运算,最后在常量池中创建字符串对象
String s2 = a.concat(b); // 变量相加,要先在堆中运算,最后在常量池中创建字符串对象
a += "World";// 变量相加,要先在堆中运算,最后在常量池中创建字符串对象
字符串转数字
使用各包装类的
parseXxx()
静态方法,但是在使用时要注意字符串必须是纯数字
String i = "150";
System.out.println(Integer.parseInt(i)); // 150
String i = "150s";
System.out.println(Integer.parseInt(i));//Exception in thread "main" java.lang.NumberFormatException: For input string: "150s"//数字格式化错误,150s中含有非数字字符
字符串转boolean
字符串转换成boolean类型时,true就是true,非true为false
public static void main(String[] args) {
String i = "true";//为true
System.out.println(Boolean.parseBoolean(i));//true
String i1 = "false";//为false
System.out.println(Boolean.parseBoolean(i1));//false
String i2 = "ksajd";//非true
System.out.println(Boolean.parseBoolean(i2));//false
}
字符串比较
==
- 比较基本数据类型:比较的是具体的值
- 比较引用数据类型:比较的是对象地址值(相当于把
对象地址
当做值来比较)
equals
Object.equals()
的实现方式也是==
,但是String重写了equals方法,所以是比较两个字符串内容是否相同(区分大小写)
public class Test {
public static void main(String[] args) {
String s = "a";
String s1 = "a";
String s2 = "A";
System.out.println(s.equals(s1));// true
System.out.println(s2.equals(s1)); // false
/**
* p1.name.equals(p2.name):由于String类重写了equals方法,所以比较的是内容
* p1.name == p2.name:虽然在堆中是两个对象地址,但是p1.name与p2.name指向
* 的字符串都存储在字符串常量池中,所以返回相同的地址
* p1.name == "张三":p2.name指向的就是字符串常量池中的张三
*/
Person p1 = new Person();
p1.name = "张三";
Person p2 = new Person();
p2.name = "张三";
System.out.println(p1.name.equals(p2.name)); // true
System.out.println(p1.name == p2.name); // true
System.out.println(p1.name == "张三"); // true
}
}
class Person {
String name;
}
== 与 equals的异同:
- 相同点:equals的实现也是==,即如果不重写equals方法,它与 == 的结果一致
- 不同点:
1)== 可以比较基本数据类型和引用类型,equals方法只能比较引用数据类型
2)== 比较的是内存地址,equals方法经过重写之后比较的是内存空间中的内容
四)练习
1、键盘录入一个字符,统计字符串中大小写字母及数字字符个数
//键盘录入一个字符串数据
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串数据:");
String s = sc.nextLine();
//定义三个统计变量,初始化值都是0
int bigCount = 0;
int smallCount = 0;
int numberCount = 0;
//遍历字符串,得到每一个字符
for(int x=0; x<s.length(); x++) {
char ch = s.charAt(x);
//拿字符进行判断
if(ch>='A'&&ch<='Z') {
bigCount++;
}else if(ch>='a'&&ch<='z') {
smallCount++;
}else if(ch>='0'&&ch<='9') {
numberCount++;
}else {
System.out.println("该字符"+ch+"非法");
}
}
//输出结果
System.out.println("大写字符:"+bigCount+"个");
System.out.println("小写字符:"+smallCount+"个");
System.out.println("数字字符:"+numberCount+"个");
2、已知用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录之后,给出相应的提示
import java.util.Scanner;
/*
思路:
1:已知用户名和密码,定义两个字符串表示即可
2:键盘录入要登录的用户名和密码,用 Scanner 实现
3:拿键盘录入的用户名、密码和已知的用户名、密码进行比较,给出相应的提示。字符串的内容比较,用
equals() 方法实现
4:用循环实现多次机会,这里的次数明确,采用for循环实现,并在登录成功的时候,使用break结束循环
*/
public class Test {
public static void main(String[] args) {
//已知用户名和密码,定义两个字符串表示即可
String username = "tom";
String pwd = "123";
//用循环实现多次机会,这里的次数明确,采用for循环实现,并在登录成功的时候,使用break结束循环
for (int i = 0; i < 3; i++) {
//键盘录入要登录的用户名和密码,用 Scanner 实现
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.nextLine();
System.out.println("请输入密码:");
String pwd = sc.nextLine();
//拿键盘录入的用户名、密码和已知的用户名、密码进行比较,给出相应的提示。字符串的内容比较,
if (name.equals(username) && pwd.equals(pwd)) {
System.out.println("登录成功");
break;
} else {
if (2 - i == 0) {
System.out.println("你的账户被锁定,请与管理员联系");
} else {
//2,1,0
//i,0,1,2
System.out.println("登录失败,你还有" + (2 - i) + "次机会");
}
}
}
}
}
3、键盘录入一个字符串,使用程序实现在控制台遍历该字符串
import java.util.Scanner;
/*
思路:
1:键盘录入一个字符串,用 Scanner 实现
2:遍历字符串,首先要能够获取到字符串中的每一个字符
public char charAt(int index):返回指定索引处的char值,字符串的索引也是从0开始的
3:遍历字符串,其次要能够获取到字符串的长度
public int length():返回此字符串的长度
数组的长度:数组名.length
字符串的长度:字符串对象.length()
4:遍历字符串的通用格式
*/
public class Test {
public static void main(String[] args) {
//键盘录入一个字符串,用 Scanner 实现
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.nextLine();
for(int i=0; i<line.length(); i++) {
System.out.println(line.charAt(i));
}
}
}
五)常见面试题
创建了几个对象
第一题
/**
* 一开始,s指向hello,所以在字符串池中创建了一个hello对象
* 将s的指向改为hi,此时又在池中创建了一个hi对象,所以创建了两个对象
*/
String s = "hello";
s = "hi";
第二题
/**
* 创建了一个对象
* 两个常量相加,编译器会做出优化:由于编译器会判断创建出来的
* 对象是否有引用指向,为了节约空间,编译器不允许创建垃圾对象,
* 所以会在底层做出优化,即将多步合成一步,这将避免空间浪费,所
* 以 String s = "hello" + " zhangsan";
* 等价于 String s = "hello zhangsan";
*/
String s = "hello" + "zhangsan";
第三题
String s = "hello";// 创建了一个hello对象
String s1 = "zhangsan"; // 创建了一个zhangsan对象
/**
* 在jdk1.8中:
* 1、首先创建一个StringBuilder对象st
* 2、调用append方法将hello对象添加到st对象:st.append("hello");
* 3、调用append方法将zhangsan对象添加到st对象:st.append("zhangsan");
* 4、调用st的toString方法,将StringBuilder类型转换为String类型
* 5、将结果返回给s2
* 所以,s2最终直接指向的是堆中的String对象,而不是常量池中的hellozhangsan对象
* 创建了3个对象:hello、zhangsan、hellozhangsan
*/
String s2 = s + s1;
注意:上述是在JDK1.8中的创建过程,JDK17中有所不同
public static void main(String[] args) {
String s = "hello";
s += "World";
System.out.println(s);
}
过程分析:
// 首先跳转DirectMethodHandle.internalMemberName(Object mh)
static Object internalMemberName(Object mh) {
return ((DirectMethodHandle)mh).member;
}
// 再跳转首先跳转DirectMethodHandle$Holder.invokeStatic(Object var0, Object var1, Object var2)
static Object invokeStatic(Object var0, Object var1, Object var2) {
Object var3 = DirectMethodHandle.internalMemberName(var0);
return MethodHandle.linkToStatic(var1, var2, (MemberName)var3);
}
// 再跳转StringConcatHolder.simpleConcat(Object first, Object second)
static String simpleConcat(Object first, Object second) {
String s1 = stringOf(first); // first = "hello"
/*
static String stringOf(Object value) {
String s;
//判断传入的第一个参数是否为null,不为null返回该字符串,即 "hello"
//若第一个参数为null,则将null转换为字符串并返回,即返回 "null"
return (value == null || (s = value.toString()) == null) ? "null" : s;
}
*/
String s2 = stringOf(second); // second = "world"
// 判断是否为空字符串,即 "",注意空字符串 "" 与 null的区别
if (s1.isEmpty()) {
// 创建第二个参数为字符串对象
return new String(s2);
}
if (s2.isEmpty()) {
// 创建第一个参数为字符串对象
return new String(s1);
}
//
long indexCoder = mix(initialCoder(), s1); //获取第一个参数的长度 s1 = "hello"
/*
// String.COMPACT_STRINGS = true
// LATIN1 = 0
static long initialCoder() {
return String.COMPACT_STRINGS ? LATIN1 : UTF16;
}
*/
/*
static long mix(long lengthCoder, String value) {
// 获取字符串长度
lengthCoder += value.length();
if (value.coder() == String.UTF16) {
/*
//COMPACT_STRINGS = true
// coder = 0
byte coder() { // 确定编码长度
return COMPACT_STRINGS ? coder : UTF16;
}
*/
lengthCoder |= UTF16;
}
return checkOverflow(lengthCoder); // 检查是否溢出
/*
private static long checkOverflow(long lengthCoder) {
if ((int)lengthCoder >= 0) {
return lengthCoder; // 返回字符串长度
}
throw new OutOfMemoryError("Overflow: String length out of range");
}
*/
}
*/
indexCoder = mix(indexCoder, s2);// 加上第二个参数的长度
byte[] buf = newArray(indexCoder); // 创建一个新的数组
/*
static byte[] newArray(long indexCoder) {
byte coder = (byte)(indexCoder >> 32);
int index = (int)indexCoder;
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, index << coder);
}
*/
indexCoder = prepend(indexCoder, buf, s2);
/*
private static long prepend(long indexCoder, byte[] buf, String value) {
indexCoder -= value.length(); // 10 - 5
if (indexCoder < UTF16) {
value.getBytes(buf, (int)indexCoder, String.LATIN1);
/*
void getBytes(byte[] dst, int dstBegin, byte coder) {
if (coder() == coder) {
System.arraycopy(value, 0, dst, dstBegin << coder, value.length);
} else { // this.coder == LATIN && coder == UTF16
StringLatin1.inflate(value, 0, dst, dstBegin, value.length);
}
}
*/
} else {
value.getBytes(buf, (int)indexCoder, String.UTF16);
}
return indexCoder;
}
*/
indexCoder = prepend(indexCoder, buf, s1);
return newString(buf, indexCoder);
/*
static String newString(byte[] buf, long indexCoder) {
// Use the private, non-copying constructor (unsafe!)
if (indexCoder == LATIN1) {
return new String(buf, String.LATIN1);
} else if (indexCoder == UTF16) {
return new String(buf, String.UTF16);
} else {
throw new InternalError("Storage is not completely initialized, " + (int)indexCoder + " bytes left");
}
}
*/
}
二、可变字符串
概述:每次对字符串进行拼接操作,都会构建一个新的String对象,既耗时又浪费空间,而可变字符串就可以解决这个问题。
由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象。例如:
public class StringDemo {
public static void main(String[] args) {
String s = "Hello";
s += "World";
System.out.println(s);
}
}
在API中对String类有这样的描述:字符串是常量,它们的值在创建后不能被更改
。
根据这句话分析我们的代码,其实总共产生了三个字符串,即"Hello"
、"World"
和"HelloWorld"
。引用变量s首先指向Hello
对象,最终指向拼接出来的新字符串对象,即HelloWord
。由此可知,如果对字符串进行拼接操作,每次拼接,都会构建一个新的String对象,既耗时,又浪费空间。为了解决这一问题,可以使用可变字符串
类。
可变字符串分为
java.lang.StringBuffer
类和java.lang.StringBuilder
类
StringBuilder
与StringBuffer
的直接父类是AbstractStringBuilder
,而且他们的数据也是存放在AbstractStringBuilder
类中的char[] value
数组中,这里的value
数组没有使用final
修饰,且value的值是存放在堆中的,所以StringBuffer和StringBiulder才是可变的,注意:StringBuffer和StringBiulder都是final修饰的类,所以不可继承
StringBuffer类
概念:可变长字符串,JDK1.0提供,运行效率慢、线程安全。可在内存中创建可变的缓冲空间,存储频繁改变的字符串,线程安全的可变字符序列。
其所有成员方法都使用synchronized关键字修饰,所以是线程安全的
StringBuffer与String的区别:
- String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低
//private final char value[];
- StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高
//char[] value; 这个放在堆.
- 在使用StringBuffer做字符串拼接,不会浪费太多的资源
构造方法:
public StringBuffer();
无参构造方法,其初始容量为 16 个字符
public StringBuffer(int capacity);
指定容量的字符串缓冲区对象
public StringBuffer(String str);
指定字符串内容的字符串缓冲区对象
成员方法:
int capacity();
返回当前容量 – 理论值
int length();
返回长度(字符数) – 实际值
(1)添加功能
StringBuffer append(String str);
把字符串添加到字符串缓冲区,并返回字符串缓冲区本身注意:该方法被多次重载,可以把任意类型的数据添加到字符串缓冲区
StringBuffer insert(int offset,String str);
在指定位置把字符串插入到字符串缓冲区,并返回本身注意:该方法被多次重载,可以在指定位置把任意类型的数据插入到字符串缓冲区
(2)删除功能
public StringBuffer deleteCharAt(int index);
删除指定位置的字符,并返回本身
public StringBuffer delete(int start,int end);
删除从指定位置开始到指定位置结束的内容,并返回本身注意:包含start,但是不包含end
(3)替换功能
public StringBuffer replace(int start,int end,String str);
从start开始到end结束用str替换,并返回本身public void setCharAt(int index,char ch);
将给定索引处的字符设置为ch
(4)反转功能
public StringBuffer reverse();
(5)截取功能
- public String substring(int star);
- public String substring(int star,int end);
注意:截取功能和前面的几个功能有所不同,其返回值是String类型,但本身并没有发生改变,即字符串本身并没有
String a = "hello World !";
String s = a.substring(5, a.length() - 1);
System.out.println("原字符串:" + a); // 原字符串:hello World
System.out.println("截取的字符串:" + s); //截取的字符串: World
String和StringBuffer的相互转换:
(1)String -> StringBuffer
- 构造方法:public StringBuffer(String str);
//返回才是StringBuffer对象,str不变
- append方法:public StringBuffer append(String str);
(2)StringBuffer -> String
构造方法:public String(StringBuffer sb);
toString方法:public String toString();
备注:StringBuilder已经覆盖重写了Object当中的toString方法。
StringBuffer与数组的区别:
两者都可以看作是一个容器,用来放置数据:
- StringBuffer的数据最终是一个字符串数据
- 数组可以放置多种类型的数据,但同一个数组中的数据必须是同一类型的
String和StringBuffer作为参数传递:
- String是常量值,一种特殊的引用类型,当它作为参数传递时,效果和基本数据类型作为参数传递一样
- StringBuffer作为参数传递,当它用来调用方法时,其形式参数的改变直接影响实际参数的改变
StringBuilder类
可变长字符串,JDK5.0提供,运行效率快、线程不安全,其方法和属性与StringBuffer几乎完全一致,只是没有使用
synchronized
关键字修饰,java.lang.StringBuilder
是一个类似于 String 的字符串缓冲区,通过某些方法调用可以改变该序列的长度和内容。它的内部拥有一个数组用来存放字符串内容,进行字符串拼接时,直接在数组中加入新内容。StringBuilder会自动维护数组的扩容。默认16字符空间,超过自动扩充
使用时机
字符串缓冲区被单个线程使用的时候
,即单个线程的时候使用StringBuilder效率较高,多个线程的时候使用StringBuffer比较安全
线程安全(多线程内容)
安全 -- 同步 -- 数据是安全的
不安全 -- 不同步 -- 效率高一些
安全和效率两者不可兼得,需要根据需求有所取舍:
安全:银行网站,医院网站
效率:新闻网站,论坛
String,StringBuffer和StringBuilder的区别:
(1)String的内容和长度都是不可变的,而StringBuffer和StringBuilder的内容和长度都是可变的
(2)StringBuffer是同步的,数据安全,但效率低;而StringBuilder是不同步的,效率高,但不安全(多线程情况)
注意:
StringBuilder和StringBuffer没有重写Object的equals方法
二者的方法基本相同,StringBuilder线程不安全,但是效率高,StringBuffer线程安全,效率低,单线程使用StringBuilder,他们都比String效率高,比较节省内存
测试StringBuffer与StringBuilder与String之间的效率
public class TestStringBuilder {
public static void main(String[] args) {
/**
* 测试String类与StringBuilder的效率
*/
StringBuilder str = new StringBuilder("abc");
String str1 = new String("abc");
/**
* 测试string与StringBuilder的效率
*/
long r = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time = System.currentTimeMillis();//获取当前系统时间
//测试string
for (int i = 0; i < 500; i++) {
str1 = str1 + i;
//System.out.println(str1.hashCode());
}
long r1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time1 = System.currentTimeMillis();//获取当前系统时间
System.out.println("运行时间:" + (time1 - time));
System.out.println("占用空间:" + (r - r1));
long r3 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time3 = System.currentTimeMillis();//获取当前系统时间
//测试StringBuilder
for (int i = 0; i < 500; i++) {
str.append(i);
//System.out.println(str.hashCode());
}
long r4 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time4 = System.currentTimeMillis();//获取当前系统时间
System.out.println("运行时间:" + (time4 - time3));
System.out.println("占用空间:" + (r3 - r4));
}
}
32、链式调用
连续调用。该方法的核心是调用了return this,把自己返回了,只要是返回自己,就可以连续调用
StringBuilder str = new StringBuilder();
str.insert(0, "张").insert(1, "三").reverse().reverse();
System.out.println(str);//输出三张
public StringBuilder reverse() { // 反转字符串
super.reverse();
return this;//返回自己
}
public StringBuilder insert(int offset, String str) {
super.insert(offset, str);//在指定位置插入字符串
return this;//返回自己
}