Java高级编程--常用类之字符串相关类String、StringBuffer、StringBuilder
Java提供了丰富的API给使用者方便其快捷高效的进行开发工作,本篇博客将介绍常用类中字符串相关类的具体使用方法,包括String、StringBuffer、StringBuilder。
目录:
☍ String类
▴ String的特性
String类:代表 字符串。Java程序中的所有字符串字面值(如"abc")都作为此类的实例实现。
☄ String是一个final类,代表不可变的字符序列(String的不可变性),不可被继承。
☄ 字符串是常量,用双引号""引起来表示。它们的值在创建之后不能更改,即调用方法对原字符串的操作使字符串改变会创建新的字符串而不会影响到原字符串本身。
☄ String对象的字符内容底层是存储在一个字符数组final char value[]中的。
☄ String实现了Serializable接口:表示字符串是支持序列化的。
☄ String实现了Comparable接口:表示字符串可以比较大小。
☄ 过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中;通过new String()方式创建字符串,此时内容存储在堆空间中。
☄ 过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中;通过new String()方式创建字符串,此时内容存储在堆空间中。
String底层代码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
....
}
▴ String对象的创建
字面量的方式赋值
说明:此时字符串数据声明在方法区中的字符串常量池中
创建对象的方式赋值(调用不同String的构造器)
说明:此时字符串变量保存的是地址值,是数据在堆空间开辟内存空间后对应的地址值
☃ 空参构造器new String(); ---- 本质上为:this.value = new char[0];
☃ String字符串参数构造器new String(String original); ---- 本质上为:this.value = original.value;
☃ char字符数组参数构造器new String(String original); ---- 本质上为:this.value = Arrays.copyOf(value, value.length);
......
public void testString2(){
//1、字面量的方式赋值
String st1 = "hello world";
//2、创建对象的方式赋值(不同String的构造器)
/*2-1:空参构造器new String();
本质上为this.value = new char[0]; 定义长度为0的字符数组
*/
String st2 = new String();
System.out.println("new String():" + st2 + "length:" + st2.length()); //new String():length:0
/*2-2:String字符串参数构造器new String(String original);
本质上为this.value = original.value
*/
String st3 = new String("serializable"); //
System.out.println("new String(String original):" + st3 + " length:"+ st3.length()); //new String(String original):serializable length:12
/*2-3:char字符数组参数构造器new String(String original);
* 本质上为this.value = Arrays.copyOf(value,value.length);
*/
String st4 = new String(new char[]{'s', 'y', 'n', 'c', 'h', 'r', 'o', 'n', 'i', 'z', 'e', 'd'});
System.out.println("new String(String original):" + st4 + " length:"+ st4.length()); //new String(String original):synchronized length:12
//对比字面量赋值方式和String构造器方式实例化String的不同
String t1 = "Java";
String t2 = "Java";
String t3 = new String("Java");
String t4 = new String("Java"); //
System.out.println("t1 == t2:" + (t1 == t2)); //true
System.out.println("t3 == t4:" + (t3 == t4)); //false
System.out.println("t1 == t3:" + (t1 == t3)); //false
Person p1 = new Person(11,"Tom");
Person p2 = new Person(11,"Tom");
System.out.println("p1==p2:" + (p1 == p2)); //false 对象存储在堆中,开辟的内存空间地址不同
System.out.println("p1.name==p2.name:" + (p1.name == p2.name)); //true,Person对象中name是以字面量的方式定义的,所以存储在方法区的字符串常量池中
}
class Person{
int age;
String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
}
创建String对象内存演示:
➹思考:
➥ String s = new String("abc");方式创建对象,在内存中建了几个对象?
两个:一个是new String()对象,一个是char value[]对应的常量池中的数据
✦ 字符串常量存储在
字符串常量池,目的是共享
✦ 字符串非常量对象存储在堆中(new String对象和操作后的string对象)
▴ String对象的不变性
字符串的不可变性,任何对字符串的操作导致字符串变化都要重新指定内存区域赋值,不能使用原有的value[]赋值
➣ 体现体现2:1、当对字符串重新赋值时,需要重新指定内存区域的值,不能使用原有的value[]赋值
➣ 体现2:当对现有的字符串进行拼接操作时,也需要重新指定内存区域赋值,不能使用原有的value[]赋值,拼接后的字符串为新的字符串
➣ 体现3:当调用String的如replace()方法修改string指定字符或字符串时,也需重新指定内存区域赋值,不能使用原有的value[]赋值,返回一个新的字符串,原字符串不变
➣ 体现4:当调用String的其他方法操作当前字符串时,返回一个新的字符串,原字符串不变
public void testString1(){
String s1 = "abc"; //字面量定义方式
String s2 = "abc";
//字面量方式定义字符串变量的值如果相等,则它们使用的是同一个地址
//原因:字面量方式定义的字符串值声明在方法区的字符串常量池中,字符串常量池不会存储相同的字符串
System.out.println(s1 == s2); //比较s1和s2的地址值;true
String st1 = new String("abc");
String st2 = new String("abc");
System.out.println(st1==st2); //比较st1和st2的地址值:false
System.out.println(st1==s1); //比较st1和s1的地址值:false
System.out.println("-----------");
//拼接时字符串的不变性
String s3 = "abc";
s3 += "def";
System.out.println(s3); //abcdef
System.out.println(s2); //不变性abc
System.out.println("-----------");
//调用String方法操作字符串时的不变性
String s4 = "abc";
String s5 = s4.replace("c","def"); //s4.replace('c','d');
System.out.println(s4); //不变性:abc
System.out.println(s5); //abdef
}
▴ String对象的拼接
/*
结论:
1、常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量
2、只要字符串拼接中有一个是变量(非final),结果就存在堆中
3、如果拼接的结果调用intern()方法,返回值就在常量池中
*/
public void test3(){
String s1 = "java";
String s2 = "web";
String s3 = "javaweb";
String s4 = "java" + "web";
String s5 = s1 + "web";
String s6 = "java" + s2;
String s7 = s1 + s2;
System.out.println("输出1:" + (s3==s4)); //true
System.out.println("输出3:" + (s3==s6)); //false
System.out.println("输出4:" + (s5==s6)); //false
System.out.println("输出5:" + (s3==s7)); //false
System.out.println("输出6:" + (s4==s7)); //false
System.out.println("输出7:" + (s5==s7)); //false
//因为s4是两个字符串拼接后以字面量的方式给s4赋值,所以他存在字符串常量池中,与s3地址一致,
// 而s5-s7都是字符串/字符变量+字符变量的拼接,其存储位置就在堆中了,存储地址发生变化
String s8 = s5.intern(); //此时返回值s8使用的是常量中已经存在的值
System.out.println("输出8:" + (s3 == s8)); //true
}
▴ String使用中的陷阱
➣ String s1 = "a";
说明:在字符串常量池中创建了一个字面量为"a"的字符串。
➣ s1 = s1 + "b";
说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符
串s1+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本
字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响
程序的性能。
➣ String s2 = "ab";s2 = "abc";
直接在字符串常量池中创建一个字面量为"ab"的字符串,赋值后丢弃原字符串"ab"对象,重新在常量池中创建一个字面量为"abc"的字符串后将地址赋给s2。
➣ String s3 = "a" + "b";
说明:s3指向字符串常量池中已经创建的"ab"的字符串。
➣ String s4 = s1.intern();
说明:说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4,如果不存在,则会在常量池中新建字符串。
例子:
下列程序运行的结果:
public class StringTest {
String str = new String("good");
char[] ch = { 't', 'e', 's', 't' };
public void change(String str,String string, char ch[]){
System.out.println(this.string == string); //true
this.str = "棒棒哒";
string = "love";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest st = new StringTest();
st.change(st.str, st.string, st.ch);
System.out.println(st.str); //test 成员变量赋值
System.out.println(st.string); //lucky String的不变性,是给参数变量赋值
System.out.println(st.ch); //best
}
}
结果说明:
public class StringTest {
String str = new String("good");
String string = new String("lucky");
char []ch = {'t','e','s','t'};
public void change(String str,String string, char ch[]){
System.out.println(this.string == string); //true
this.str = "棒棒哒";
string = "love"; //参数string字面量方式赋值,内容存在常量池中
ch[0] = 'b';
System.out.println("内:"+string); //love
System.out.println(this.string == string); //false
String s = "love";
System.out.println(string == s); //true string和s在同一个常量池内存中
System.out.println("---------------------------");
/*说明:对于String string的情况类似于:
String s1 = new String("lucky"); //对应成员变量this.string,内容在堆空间中
String s2 = s1; //对应参数string,指向同一个堆内存地址
s2 = "love"; //字面量方式赋值,内容存在常量池中
此种情况是String的特殊情况,因为其字面量方式赋值和new新建内容存储位置不同导致,
其他如本例中的ch[]不会出现这种情况,它们的值都是在堆空间中
*/
}
public static void main(String[] args) {
StringTest st = new StringTest();
st.change(st.str, st.string, st.ch);
System.out.println(st.str); //棒棒哒 成员变量赋值
System.out.println(st.string); //lucky 是给参数变量以字面量方式赋值
System.out.println(st.ch); //best
}
}
/*输出:
true
内:love
false
true
---------------------------
棒棒哒
lucky
best
*/
☍ String与其他类型数据的转换
▾ 字符串与基本数据类型、包装类之间的转换
字符串 ➟ 基本数据类型、包装类
➣ Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
➣ 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应
的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。
基本数据类型、包装类 ➟ 字符串
➣ 调用String类的public String valueOf(int n)可将int型转换为字符串。
➣ 相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double
d)、valueOf(boolean b)可由参数的相应类型到字符串的转换。
/**
* @Description:
* 基本数据类型与包装类之间的转换:要转换的基本数据类型包装类.parseXxx(String string)
* 1、string-->基本数据类型 包装类.parseXxx(String string)
* 2、基本数据类型-->string String.valueOf(基本数据类型)
*/
public void stingAndBaseNum(){
String s1 = "123";
//字符串转为基本数据类型 包装类.parseXxx(String string)
int num1 = Integer.parseInt(s1);
System.out.println(num1);
double d1 = Double.parseDouble(s1);
System.out.println(d1);
//2、包装类转换为String字符串:String.valueOf(包装类对象)
int i1 = 2020;
char c1 = '中';
System.out.println(String.valueOf(i1));
System.out.println(String.valueOf(c1));
System.out.println((s1+"")==(String.valueOf(123)));//false
}
▾ String与字符数组转换
字符数组 ➟ 字符串
➣ String 类的构造器:String(char[]) 和 和 String(char[],int offset,int
length) 分别用字符数组中的全部字符和部分字符创建字符串对象。
字符串 ➟ 字符数组
➣ public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。
➣ public void getChars(int srcBegin, int srcEnd, char[] dst,
int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。
/**
* @Description:
* 字符串与字符数组间的转换
* 1、string-->chars[] string.toCharArray();
* 2、chars[]-->string new String(chars[])构造器
*/
public void stringAndChars(){
String s1 = "Java中国 2020";
//string转chars[]数组1
char c1[] = s1.toCharArray();
for (int i = 0; i < c1.length; i++) {
System.out.print(c1[i] + "|");
}
System.out.println();
//string转chars[]数组1
char c2[] = new char[s1.length()];
s1.getChars(0,s1.length(),c2,0);
for (int i = 0; i < c2.length; i++) {
System.out.print(c2[i] + "|");
}
System.out.println();
//chars[]数组转string
String s2 = new String(c1);
System.out.println(s2);
}
▾ String与字节数组转换
字节数组 ➟ 字符串
➣ String(byte[]):通过String构造器使用平台的默认字符集解码指定的 byte 数组,构
造一个新的 String。
➣ String(byte[],String charsetName):通过String构造器使用指定字符集解码指定的byte数组,构造一个新的String。
➣ String(byte[],int offset,int length):从数组指定起始位置offset开始取length个字节构造一个字符串对象。
字符串 ➟ 字节数组
➣ public byte[] getBytes():使用平台的默认字符集将此String编码为byte序列,并将结果存储到一个新的 byte[] 数组中。
➣ public byte[] getBytes(String charsetName):使用指定的字符集将此String编码到byte序列,并将结果存储到新的byte[]数组。。
public void stringAndBytes() throws UnsupportedEncodingException {
String s1 = "Java中国 2020";
//string字符串转bytes[]数组,默认字符集为系统设置的(此处utf-8)
byte []b1 = s1.getBytes();
System.out.println(Arrays.toString(b1));
//指定string转换为bytes[]的字符集规则
byte b2[] = s1.getBytes("gbk");
System.out.println(Arrays.toString(b2)); //Arrays.toString(数组);将数组转为带[]字符串
//byte b3[] = s1.getBytes("gbs"); //报UnsupportedEncodingException字符集异常
//bytes[]数组转为string字符串
System.out.println(new String(b1));
//bytes[]数组转为string字符串,字符集不对应
System.out.println(new String(b2)); //出现乱码
//bytes[]数组转为string字符串,指定对应字符集
System.out.println(new String(b2,"gbk"));
}
☍ StringBuffer与StringBuilder
☄ java.lang.StringBuffer和java.lang.StringBuilder代表可变的字符序列
,JDK1.0中声明,在可以对字符串内容进行增删,此时不会产生新的对象。
☄ 两者有很多方法与String相同。
☄ 作为参数传递时,方法内部可以改变值。(与上面String举得例子相反)
☃ StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:
➣ StringBuffer() :初始为容量为16的字符串缓冲区
➣ StringBuffer(int size) :构造指定容量的字符串缓冲区
➣ StringBuffer(String str) :将内容初始化为指定字符串内容
☃ StringBuilder与StringBuffer类似
String、StringBuffer、StringBuilder的异同:
☄ String:不可变的字符序列;底层使用char[]数组存储。
☄ StringBuffer:可变的字符序列;线程安全,相率相对较低;底层使用char[]数组存储。
☄ StringBuilder:可变的字符序列;jdk5.0新增,线程不安全,效率相对较高;底层使用char[]数组存储。
✦ 作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值
/*
源码分析:
String str = new String(); ---- new char[0];
String str1 = new String("abc"); ---- new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer(); ---- new char[16] 默认创建了一个长度为16的char数组
sb1.append('a'); ---- value[0] = 'a';
sb2.append('b'); ---- value[1] ='b';
System.out.println(sb1.length); //sb1的长度为0 注意不是16,16是char []value的长度。
StringBuffer sb2 = new String("abc"); ---- char []value = new char["abc".length + 16];
StringBuilder与StringBuffer用法十分类似,大多数方法也相同,同时它们也拥有String的大多数方法
问题1: System.out.println(sb2.length); //3
问题2: 扩容问题:如果添加的数据超过底层数组初始空间大小,需要扩容数组
默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新数组中
建议:开发中建议使用StringBuffer(int capacity) 或 StringBuilder(int capacity)
避免数组扩容造成的效率降低
*/
public class StringBufferAndStringBuilder {
@Test
public void test1(){
StringBuffer sb1 = new StringBuffer("abc123");
sb1.setCharAt(0,'m');
sb1.append("地球");
System.out.println(sb1); //mbc,对StringBuffer对象的操作会影响到原StringBuffer对象
StringBuilder sb2 = new StringBuilder("abc123");
sb2.setCharAt(0,'m');
sb2.append("中国");
System.out.println(sb2);
}
▾ StringBuffer和StringBuilder类的常用方法
序号 | 方法 | 说明 |
---|---|---|
1 | append(xxx) | 提供了很多的append()方法,用于进行字符串拼接 |
2 | deleteCharAt(int index) | 删除指定索引位置的字符 |
3 | delete(int start,int end) | 删除指定范围的内容 |
4 | setCharAt(int index, char ch) | 设置指定索引位置的字符值 |
5 | replace(int start, int end, String str) | 替换[start,end)位置的字符串为另一个字符串str |
6 | insert(int offset, xxx) | 在指定索引位置插入元素xxx |
7 | reverse() | 逆转字符序列 |
public void methods(){
StringBuffer sb1 = new StringBuffer("abc");
//末尾添加字符/字符串
sb1.append(1234);
sb1.append('中');
sb1.append("$@#");
System.out.println(sb1); //abc1234中$@#
//删除指定索引位置的字符
sb1.deleteCharAt(2);
System.out.println(sb1); //ab1234中$@#
//设置指定索引位置的字符值
sb1.setCharAt(0,'A');
//删除从startIndex,到endIndex位置的字符,[startIndex,endIndex)前闭后开
sb1.delete(3,5);
System.out.println(sb1); //Ab14中$@#
//替换[start,end)位置的字符串为另一个字符串str:replace(int start, int end, String str)
sb1.replace(5,8,"国");
System.out.println(sb1); //Ab14中国
//在指定索引位置插入元素(数字,字符,字符串,Boolean)
sb1.insert(4,"China");
System.out.println(sb1); //Ab14China中国
//反转字符序列
sb1.reverse();
System.out.println(sb1); //国中anihC41bA
}
▾ String、StringBuffer和StringBuilder类的执行效率对比
执行效率:StringBuilder > StringBuffer >> String
/**
* @Description: String,StringBuffer,StringBuilder三者执行效率对比
* StringBuilder > StringBuffer >> String
* 原因:StringBuffer和StringBuilder是多线程的所以效率远高于String
* StringBuffer是线程安全的,StringBuilder没有进行线程安全处理,所以StringBuilder效率较高一点
*/
@Test
public void strCompare(){
//初始设置
long startTime = 0L;
long endTime = 0L;
String text = "";
StringBuffer buffer = new StringBuffer("");
StringBuilder builder = new StringBuilder("");
//开始对比
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
//StringBuffer的执行时间:35
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
//StringBuilder的执行时间:15
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
//String的执行时间:1342
}
String类的常用方法↷传送门
本博客与CSDN博客༺ཌ༈君☠纤༈ད༻同步发布