1、String类
1.1、String的特性
String类:代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
- String是一个
final
类,代表不可变的字符序列,不能被继承- 当对字符串重新赋值时,需要重写指定区域赋值,不能使用原有的
value
进行赋值。 - 当对现有的字符串进行连接操作时,也需要重新在指定内存区域赋值,不能使用原有的
value
进行赋值。 - 当调用
String
的replace()
方法修改字符或字符串时,需要重写指定区域赋值
- 当对字符串重新赋值时,需要重写指定区域赋值,不能使用原有的
- 字符串是常量,用双引号引起来表示,它们的值在创建之后不能更改。
- String内部定义了
final char[] value
用于存储字符串数据 - String实现了
Serializable
接口:表示字符串是支持序列化的 - 实现了
Comparable
接口:表示String可以比较大小 - 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
- 字符串池中是不会存储相同内容的字符串的
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
1.2、String的实例化方式
1.2.1、通过字面量定义的方式
反正要想修改字符串,就得重新制定区域新造,不能在原来的
value
上修改,因此字符串的不可变性就体现出来了
1、对字符串重新赋值
直接通过字面量的方式, 此时的s1和s2的数据声明在方法区中的字符串常量池中,字符串常量池中是不会存储相同内容的字符串的。
public class test1 {
@Test
public void test01(){
String s1 = "abc";//字面量定义的方式
String s2 = "abc";
System.out.println(s1==s2); //true,==号,对于引用类型来说,比较的是s1和s2的地址值
s1 = "hello" //对字符串重新赋值时,需要重写指定区域赋值
System.out.println(s1==s2); //false
}
}
内存解析:(这里的内存结构随JDK版本的不同有所不同):
2、对现有的字符串进行连接操作
当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value
进行赋值
String s3 = "abc";
s3 += "def"; //即会在方法区/常量池重新造一个abc再加上def
System.out.printLn(s3) ; //abcdef
System.out.println(s2); //abc
3、调用String的replace()
方法修改指定字符或字符串
当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
String s4 = "abc";
String s5 = s4.replace('a','m');//即会在方法区/常量池重新造一个abc再进行修改
System.out.printLn(s4);//abc
System.out.println(s5);//mbc
1.2.2、通过 new + 构造器的方式
通过new
+ 构造器的方式:变量保存的是数据在堆空间中开辟空间以后对应的地址值。
String str = "hello";
//本质上this.value = new char[0];
String s1 = new String();
//this.value = original.value;
String s2 = new String(String original);
//this.value = Arrays.copyOf(value, value.length);
String s3 = new String(char[] a);
String s4 = new String(char[] a,int startIndex,int count);
1.3、字符串对象是如何存储的
String str1 = “abc”;
与String str2 = new String(“abc”);
的区别?
String str1="abc"
可能创建一个对象或者不创建对象,如果"abc"
这个字符串在java String
池(StringTable)里不存在,会在java String池创建这个一个String对象(“abc”)。如果已经存在,str1直接reference to
这个String池里的对象。String str2 = new String("abc")
至少创建一个对象,也可能两个。因为用到new关键字,会在堆创建一个 str2 的String 对象,它的value
值是直接复制常量池中已存在的"abc"。同时,并且如果"abc"这个字符串在java String池里不存在,也同时会在java String池创建这个一个String对象(“abc”).- String 有一个
intern()
方法,native的,用来检测在String pool是否已经有这个String存在。
案例
public void test3(){
String s1 = "javaEE";
String s2 = "hadoop";
string s3 = "javaEEhadoop";
string s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop" ;
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.printLn(s3 == s5);//false
System.out.printLn(s3 == s6);//false
System.out.printLn(s3 == s7);//false
System.out.printLn(s5 == s6);//false
System.out.printLn(s5 == s7);//false
System.out.println(s6 == s7);//false
string s8 = s6.intern(); //返回值得到的s8使用的是常量值中已经存在的"javaEEhadoop”
System.out.printLn(s3 == s8);//true
}
public void test4(){
string s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false
final String s4 = "javaEE";//s4:常量
string s5 = s4 + "hadoop"; //常量与常量的拼接
System.out.println(s1 == s5);//true
}
结论:
- 常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量,有相同内容直接拿来用即可
- 只要其中有一个是变量,结果就在堆中 ,他就去
new
一个,并指向常量池里面存在的 - 如果拼接的结果调用
intern()
方法,返回值就在常量池中
String s=new String(“abc”);
方式创建对象,在内存中创建了几个对象?
创建了两个:一个是堆空间中new结构,另一个是char[]
对应常量池中的数据:“abc”。
案例
//尝试运行下面代码
public class StringTest {
String str=new String("good");
char[] ch={'t','e','s','t'};
public void change(String str,char ch[]){//值传递,引用用数据类型本来可以改。但是字符申比较特殊
str="test ok"; //你要修改字符串,就必须新造一个修改
ch[0]='b'; //引用数据类型,改了全改
}
public static void main(String[] args) {
StringTest ex=new StringTest();
ex.change(ex.str,ex.ch);
System.out.println(ex.str);//good
System.out.println(ex.ch);//best
}
}
解释: 对于字符串str
来说,方法形参里面的str
只不过是一个拷贝的副本,可以看做是str1
,只是通过值传递两者都指向good罢了,要修改字符串,只能新造一个,不能在原有基础上继续修改,图解:
补充:为什么大家都说Java中只有值传递?
zhuanlan.zhihu.com/p/102048219
1.4、关于对象里面的String属性
Person p1 = new Person("Tom",12);
Person p2 = new Person("Tom",12);
System.out.println(p1.name.equals(p2.name));//true
System.out.println(p1.name == p2.name);//true
p1.name = "Jerry"; //要改只能重新造一个,在改变指向
System.out.println(p2.name);//Tom,不可变性,
==
对于基本数据类型,比较的是内容,对于引用数据类型,比较的是地址值equals
只能用于引用数据类型,其中String重写了equlas
方法,比较的是内容name
这个字符串属性的值其实是保存在方法区常量池的(字面量定义的方式),TOM只有一个,所以他们是true
图解:
2、String常用方法
2.1、常用方法1
int length():返回字符串的长度:return value.length
char charAt(int index):返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length==0
String toLowerCase():使用默认的语言环境,将String中的所有字符转换为小写
String toUpperCase():使用默认的语言环境,将String中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。等于用"+"。
int compareTo(String anotherString):比较两个字符串的大小。
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取。
String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取,截取至endIndex。(左闭右开)
代码练习:
public class StringMethodTest {
@Test
public void test1() {
String s1 = "HelloWorld";
System.out.println(s1.length());
System.out.println(s1.charAt(8));//l
System.out.println(s1.charAt(0));//H
System.out.println(s1.isEmpty());//判断字符串是否为空,false
String s2 = s1.toLowerCase();//转换小写
System.out.println(s2);//helloworld
System.out.println(s1);//HelloWorld s1是不可变的
String s3 = s1.toUpperCase();//转换大写
System.out.println(s3);//HELLOWORLD
String s4=" Hel l o Wo r ld ";
String s5 = s4.trim();//忽略前导和尾部空白
System.out.println(s4);// Hel l o Wo r ld
System.out.println(s5);//Hel l o Wo r ld
System.out.println(s3.equals(s1));//false
System.out.println(s3.equalsIgnoreCase(s1));//true 忽略大小写,比较内容
String s6="abc";
String s7="abe";
//涉及到字符串的排序
System.out.println(s6.compareTo(s7));//-2 前面小
System.out.println(s7.compareTo(s6));//2 前面大
System.out.println(s1.substring(5));//World
System.out.println(s1.substring(5, 7));//Wo
}
}
2.2、常用方法2
boolean endsWith(String suffix):测试此字符串是否指定的后缀结束
boolean startWith(String prefix):测试此字符串是否以指定的的前缀开始
boolean startsWith(String prefix,int toffset):测试此字符串从指定索引开始子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的char值序列时,返回true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str,int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边处出现的索引
int lastIndexOf(String str,int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
练习:
public class StringMethodTest1 {
@Test
public void test(){
String s1="helloworld";
//测试此字符串是否指定的后缀结束
System.out.println(s1.endsWith("ld"));//true
System.out.println(s1.endsWith("a"));//false
//测试此字符串是否以指定的的前缀开始
System.out.println(s1.startsWith("h"));//true
System.out.println(s1.startsWith("ha"));//false
//测试此字符串从指定索引开始子字符串是否以指定前缀开始
System.out.println(s1.startsWith("ll", 2));//true
System.out.println(s1.startsWith("el", 2));//false
//当且仅当此字符串包含指定的char值序列时,返回true
String s2="wo";
String s3="wa";
System.out.println(s1.contains(s2));//true
System.out.println(s1.contains(s3));//false
//返回指定子字符串在此字符串中第一次出现处的索引
System.out.println(s1.indexOf(s2));//5
System.out.println(s1.indexOf(s3));//-1 未找到返回-1
//返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
System.out.println(s1.indexOf(s2, 6));//-1 未找到 返回-1
System.out.println(s1.indexOf(s2, 2));//5
System.out.println(s1.indexOf(s2, 4));//5
//返回指定子字符串在此字符串中最右边处出现的索引
String s4="hellororworld";
String s5="or";
System.out.println(s4.lastIndexOf(s5));//9
//返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
System.out.println(s4.lastIndexOf(s5, 8));//6
}
}
注:
indexOf
和lastIndexOf
方法如果未找到都是返回-1- 什么情况下,
indexOf(str)
和lastIndexOf(str)
返回值相同?- 情况一:存在唯一的一个str
- 情况二:不存在str
2.3、常用方法3
//替换:
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替换此字符串匹配给定的正则表达式的第一个子字符串。
//匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
//切片:
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex,int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
练习:
public class test01 {
@Test
public void test01(){
String str1="温州牛逼温州";
String str2=str1.replace("温州","宁波");
System.out.println(str1);
System.out.println(str2);
System.out.println("*************************");
String str = "12hello34world5java7891mysql456";
//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
System.out.println(string);
str = "12345";
//判断str字符串中是否全部有数字组成,即有1-n个数字组成
boolean matches = str.matches("\\d+");
System.out.println(matches);
System.out.println("*************************");
str = "hello|world|java";
String[] strs = str.split("\\|");
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
System.out.println();
str2 = "hello.world.java";
String[] strs2 = str2.split("\\.");
for (int i = 0; i < strs2.length; i++) {
System.out.println(strs2[i]);
}
}
}
2.4、格式化字符串
我们知道输出格式化数字可以使用 printf()
和 format()
方法。
String 类使用静态方法 format()
返回一个String 对象而不是 PrintStream
对象。
String 类的静态方法 format()
能用来创建可复用的格式化字符串,而不仅仅是用于一次打印输出。
如下所示:
System.out.printf("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
"is %s", floatVar, intVar, stringVar);
你也可以这样写
String fs;
fs = String.format("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
" %s", floatVar, intVar, stringVar);
3 、类型转换
3.1、String与基本数据类型、包装类之间的转换
- String --> 基本数据类型、包装类:调用包装类的静态方法:
parseXxx(str)
- 基本数据类型、包装类 --> String:调用String重载的
valueOf(xxx)
public void test1(){
String str1 = "123";
// int num = (int)str1;//错误的
int num = Integer.parseInt(str1);
String str2 = String.valueOf(num);//"123"
String str3 = num + "";
System.out.println(str1 == str3); //false,有变量参与
}
3.2、String 与 char[] 字符数组之间的转换
- String --> char[]:调用String的
toCharArray()
- char[] --> String:调用String的构造器
@Test
public void test2(){
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{'h','e','l','l','o'};
String str2 = new String(arr);
System.out.println(str2);
}
3.3、String 与 byte[] 字节数组之间的转换
- 编码:String --> byte[]:调用String的
getBytes()
,字符串 -->字节 (看得懂 —>看不懂的二进制数据) - 解码:byte[] --> String:调用String的构造器,编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 —> 看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes(); //使用默认的字符集进行编码。不写就是默认的
System.out.println(Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
System.out.println(Arrays.toString(gbks));
String str2 = new String(bytes);//使用默认的字符集进行解码。没有写,所以是按照默认来的
System.out.println(str2);
String str3 = new String(gbks);//没有写,所以是按照默认来的解码,而gbks这个序列是按照gbk编码来的
System.out.println(str3); //出现乱码。原因:编码集和解码集不一致!
String str4 = new String(gbks, "gbk");
System. out. println(str4);//没有出现乱码。原因:编码集和解码柴一致!
}
4、StringBuffer类和StringBuilder类
4.1、概述
java.lang.StringBuffer
代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。- 很多方法与String相同。
- 作为参数传递时,方法内部可以改变值。
- 当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
- 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象
继承体系
在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer
。 StringBuffer
类不同于String,其对象必须使用构造器生成。有三个构造器:
StringBuffer()
:初始容量为16的字符串缓冲区StringBuffer(int size)
:构造指定容量size.length()+16
的字符串缓冲区StringBuffer(String str)
:将内容初始化为指定字符串内容
String s = new String("我喜欢学习");
StringBuffer buffer = new StringBuffer(s);
buffer.append("数学"); //不会返回新的字符串,而是直接在原有字符串上进行修改
System.out.println(buffer);//我喜欢学习数学
String str = null;
StringBuffer sb = new StringBuffer();//char[] value = new char[16]
sb.append(str);//value[0]='null';
System.out.println(sb.length());//4
System.out.println(sb);//null
StringBuffer sb1 = new StringBuffer(str);
System.out.println(sb1);//java.lang.NullPointerException,null.length=NullPointerException
为什么StringBuffer sb1 = new StringBuffer(str);
结果会抛出空指针异常,查看构造器源码:
public StringBuffer() {
super(16);//没有空指针异常
}
public StringBuffer(String str) {
super(str.length() + 16); //str=null,str.length=空指针异常
append(str);
}
4.2、StringBuffer常用方法
参考:www.runoob.com/java/java-s…
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接。
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start,int end,String str):把[start,end]位置替换为str
StringBuffer insert(int offest,xxx):在指定位置插入xxx
StringBuffer reverse():把当前字符序列逆转
//方法:类似于String的方法
public int indexOf(String str)
public String substring(int start,int end)
public int length()
public char charAt(int n)
public void setCharAt(int n,char ch)
注意:
-
当append和insert时,如果原来value数组长度不够,可扩容。
-
如上这些方法支持方法链操作,方法链的原理:
@Override public StringBuilder append(String str) { super.append(str); return this; }
练习
public void test2(){
StringBuffer s1 = new StringBuffer("abc");
s1.append(1);//添加
s1.append('1');
System.out.println(s1);//abc11
//s1.delete(2,4);//删除操作
//System.out.println(s1);//ab1
s1.replace(2,4,"hello");//替换
System.out.println(s1);//abhello1
s1.insert(2,false);//插入
System.out.println(s1);//abfalsehello1
s1.reverse();//反转
System.out.println(s1);//1olleheslafba
}
总结:
- 增:append(xxx)
- 删:delet(int start,int end)
- 改:
setCharAt(int n,char ch)
/replace(int start,int end,String str)
- 查:char charAt(int n)
- 插:insert(int offset,xxx)
- 长度:length();
- 遍历:for() + charAt() / toString()
4.3、StringBuffer源码分析
String str new String(); //底层就是:new char[0];
String str1=new String("abc");//底层:new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16]; 底层创建了一个长度是16的数组
System.out.println(sb1.length);//0
sb1.append('a');//value[0]='a';
sb1.append('b');//value[1]='b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length()+16];
System.out.println(sb2.length()); //3
扩容问题:如果要添加的数据底层数组放不下了,那就需要扩容底层的数组。
- 默认情况下,自动扩容为原来的2倍+2,同时将原有的数组中的元素复制到新的数组中。
- 在开发中建议使用这样的构造器:
StringBuffer(int capacity)
或StringBuilder(int capacity)
@Test
public void test1(){
StringBuffer sb1 = new StringBuffer("abc");
sb1.setCharAt(O,'m');//不返回一个新的字符串
System.out.println(sb1);//mbc,可变
StringBuffer sb2 = new StringBuffer();
System.out.println(sb2.length());//0
}
5、StringBuilder类
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
StringBuilder
和 StringBuffer
非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样
public class RunoobTest{
public static void main(String args[]){
StringBuilder sb = new StringBuilder(10);
sb.append("Runoob..");
System.out.println(sb);
sb.append("!");
System.out.println(sb);
sb.insert(8, "Java");
System.out.println(sb);
sb.delete(5,8);
System.out.println(sb);
}
}
图解
以上实例编译运行结果如下:
Runoob..
Runoob..!
Runoob..Java!
RunooJava!
6、总结
String、StringBuffer、StringBuilder三者的异同?
- String:不可变的字符序列;底层使用
char[]
存储 - StringBuffer:可变的字符序列;线程安全的,效率低;底层使用
char[]
存储 - StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用
char[]
存储
注意:
- 作为参数传递的话,方法内部
String
不会改变其值,StringBuffer
和StringBuilder
会改变其值。 - 多线程考虑安全的情况下,可以采用 StringBuffer,否则建议使用StringBuilder
7、String、StringBuffer、StringBuilder效率对比
从高到低排列:StringBuilder > StringBuffer > String
@Test
public void test3(){
//初始设置
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));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String的执行时间:" + (endTime - startTime));
}
结果:
StringBuffer的执行时间:9
StringBuilder的执行时间:0
String的执行时间:3060