一、String
1、 String的不可变性
String:字符串,使用一对""引起来表示;
1、String类 是声明为final的,不可被继承;
2、String实现了Serializable接口;表示字符串是支持序列化;
实现了Comparable接口;表示String可以比较大小;
3、String内部定义了final char[] value;用于存储字符串数据;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[]; //字符数组保存字符串的值
4、String:代表不可变的字符序列。简称:不可变性
【体现】
1、当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值;
String s1 = "abc";
s1 = "def";
2、当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值;
String s1 = "abc";
s1+= "cdf";
3、当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值;
String s3 = s2.replace("a", "m");
5、通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中;
6、字符串常量池中是不会存储相同内容的字符串的;
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); //true //比较s1和s2的地址值
2、String对象的创建
String str = "Hello"; //在常量池取值
String s1 = new String();//本质就是 this.value = new Char[0];
String s1 = new String(String original);//this.value = original.value;
1、通过字面量定义的方式,此时下面代码例子的s1和s2的数据 javaEE 声明在方法区;
2、通过new + 构造器的方式,此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值;
【== 判断是否是相同的地址值,String中的重写Obeject的equal方法比较内容是否相等】
String s1 = "javaEE";
String s2 = "javaEE";
String s3 = new String("javaEE") ;
String s4 = new String("javaEE") ;
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
System.out.println(s1 == s4); //false
System.out.println(s3 == s4); //false
//若用equals比较,则全部为true;内容相等就为true;
3、字符串作为某一个类构造器中的参数传递,如果它是字面量创建的,那么相同的内容的地址值是一样的;(如果参数是new String(“Tom”),同样会在堆里开辟空间)
4、String s1 = new String(“abc”); 在内存中创建了几个对象?
【两个,一个是堆空间的new结构,另一个是char[]对应的常量池中的数据:”abc”】
5、字符串拼接
String s1 = "hello";
String s2 = "world";
String s3 = "hello"+"world"; //同“helloworld”
String s4 = s1+"world";
String s5 = s1+s2;
String s6 = (s1+s2).intern();
System.out.println(s3 == s4); //false
System.out.println(s3 == s5); //false
System.out.println(s4 == s5); //false
System.out.println(s3== s6);//true
-
常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量;
-
只有其中有一个是变量,结果在堆中(堆的地址);
-
如果拼接的结果用intern()方法,返回值就在常量池中(常量池的地址);
-
如果是final修饰的变量,那么它就是一个常量;此时就成了常量与常量拼接;
final String s1 = "abc"; String s2 = "abcdef"; String s3 = s1 + "def"; System.out.println(s2==s3) //true
【实例】
public class Demo {
String s1 = new String("good");
int a = 10;
char[] c = {'t', 'e', 's', 't'};
public void change(String s1,char[] c){
s1 = "test ok";
c[0] = 'b';
}
@Test
public void test(){
Demo demo = new Demo();
demo.change(demo.s1,demo.c);
System.out.println(demo.s1); //输出 good
System.out.println(demo.c); //输出 best
}
}
1、实例变量 s1是通过new + 构造器赋值的,所以它的地址值在堆中;
2、当你通过change方法把s1传进去后,形参s1指向实例变量s1的地址值(图中虚线所示),但你在方法中,把形参s1通过字面量赋值后,它的地址值就是在常量池中了;
3、char c作为参数传进去后,修改的是char[0],自然char[0]的值就会变了;
JVM中字符串常量池存放位置说明;
- jdk 1.6 (jdk 6.0 ,java 6.0):字符串常量池存储在方法区(永久区);
- jdk 1.7:字符串常量池存储在堆空间;
- jdk 1.8:字符串常量池存储在方法区(元空间);
3、String的常用方法
3.1 字符串操作
操作字符:
【代码例子】
- int length():返回字符串的长度: return value.length
String s1 = "HelloWorld"
//返回字符串的长度,底层char[]的长度
System.out.println(s1.length()); //输出10
- char charAt(int index): 返回某索引处的字符return value[index]
String s1 = "HelloWorld"
//我们不能直接用下标调用String中的char[]数组;
//返回某索引出的字符 return value[index];
System.out.println(s1.charAt(0)); //输出H
System.out.println(s1.charAt(9)); //输出d
System.out.println(s1.charAt(10)); //异常:String index out of range: 10
- boolean isEmpty():判断是否是空字符串:return value.length == 0
String s1 = "HelloWorld"
System.out.println(s1.isEmpty());//输出false
String s2 = "";
System.out.println(s2.isEmpty()); //输出true
-
String toLowerCase():使用默认语言环境,将 String 中的所字符转换为小写
-
String toUpperCase():使用默认语言环境,将 String 中的所字符转换为大写
String s1 = "HelloWorld"
String s2 = s1.toLowerCase(); //s1是可不变的
String s3 = s1.toUpperCase();
System.out.println(s2); //输出helloworld
System.out.println(s3);//输出HELLOWORLD
-
String trim():
返回字符串的副本,忽略前导空白和尾部空白【可以用于默认去掉用户不小心输入的空格】
String s3 = " hello world ";
String s4 = s3.trim();
System.out.println("s3 = ----"+s3+"----");
System.out.println("s4 = ----"+s4+"----");
//输出,可以看到s4去掉了s3首尾的空格
s3 = ---- hello world ----
s4 = ----hello world----
-
boolean equals(Object obj):比较字符串的内容是否相同
-
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String s1 = "HelloWorld";
String s2 = "helloworld";
System.out.println(s1.equals(s2));//false
System.out.println(s1.equalsIgnoreCase(s2));//true
- String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
String s1 = "abc";
String s2 = s1.concat("def");
System.out.println(s2); //输出“abcdef”
- int compareTo(String anotherString):比较两个字符串的大小
//实际就是a=97,b=98,等等,然后每一个字符都进行比较大小,最后的返回一个总差值
String s1 = "abc";
String s2 = new String("abe");
System.out.println(s1.compareTo(s2)); //输出-2
System.out.println(s2.compareTo(s1)); //输出2
- String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String s1 = "你好世界";
String s2 = s1.substring(2); //从char[2]开始取值
System.out.println(s2); //输出子串 世界
System.out.println(s1); //原来的不会变,输出 你好世界
-
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
【一般都是左闭右开区间】
String s1 = "你好世界";
String s3 = s1.substring(1, 3); //从char[1]-到char[2]
System.out.println(s3); //输出 好世
判断字符:
- boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
- boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
- boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
【代码实例】
String str1 = "helloworld";
boolean b1 = str1.endsWith("rld");
System.out.println(b1); //true
boolean b2 = str1.startsWith("He");
System.out.println(b2); //false
//从下标为2开始的字符是不是"ll"
boolean b3 = str1.startsWith("ll",2);
System.out.println(b3); //true
3.2 查找字符串中的字符
- 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):返回指 定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
【代码实例】
String str1 = "helloworld";
String str2 = "wor";
System.out.println(str1.contains(str2)); //true
System.out.println(str1.indexOf("lo")); //返回 3
System.out.println(str1.indexOf("lol")); //返回 -1
System.out.println(str1.indexOf("lo",5)); //返回-1
String str3 = "hellorworld";
System.out.println(str3.lastIndexOf("or"));//返回 7
System.out.println(str3.lastIndexOf("or",6));//返回 4
注:indexOf和lastIndexOf方法如果未找到都是返回-1
【什么情况下,indexOf和lastIndexOf的返回值相同?】
①存在唯一的一个str;②不存在str;
3.3 字符串操作方法
1、替换
- 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 替换此字符串匹配给定的正则表达式的第一个子字符串。
String str1 = "北京大学北大学生";
String str2 = str1.replace('北', '南');
System.out.println(str1);
System.out.println(str2);
String str3 = str1.replace("北京", "广州");
System.out.println(str3);
System.out.println("正则表达式相关方法:");
String str = "12chx345hello6world";
String string1 = str.replaceAll("\\d+", ",");
String string2 = str.replaceAll("\\d+", ",") //以“,”替换数字
.replaceAll("^,|,$",""); //以“”替换首尾的“,”
System.out.println(string1);
System.out.println(string2);
//输出
北京大学北大学生
南京大学南大学生
广州大学北大学生
正则表达式相关方法输出:
,chx,hello,world
chx,hello,world
2、匹配
- boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
String str = "12345";
//判断str字符串中是否全部由数字组成;
boolean matches = str.matches("\\d+");
System.out.println(matches); //输出true
String tel = "0571-4534249";
//判断这是否是一个杭州的固定电话;
boolean result = tel.matches("0571-\\d{7,8}");
System.out.println(result); //输出true
3、切片
- String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
- String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
String str = "hello#world#java";
String[] strs = str.split("\\#"); //以 # 切片
for (String s : strs) {
System.out.print(s+"\t");
}
//输出
hello world java
4、String与基本数据类型转换
包装类?
4.1 String → 基本数据类型、包装类
【调用包装类的静态方法:parseXxx(str)】
String str1 = "123";
//int num = (int) str1; 这是不行的,只能有子父类的关系的才能这样;
int i = Integer.parseInt(str1);
double v = Double.parseDouble(str1);
System.out.println(i);
System.out.println(v);
//输出
123
123.0
**********
//转化为boolean类型,除了字符串的内容是“true”,输出true,其余的字符串都是false
String s1 = "a";
String s2 = "true";
System.out.println(Boolean.parseBoolean(s1));//false
System.out.println(Boolean.parseBoolean(s2)); //true
4.2 基本数据类型、包装类 → String
【调用String重载的valueof(xxx)】
String s1 = String.valueOf(123);//int类型转换string
String s2 = String.valueOf(false);//boolean类型转换string
String s3 = String.valueOf(12.6F);//double类型转换string
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
//输出
123
false
12.6
*******
//直接用 + “”
String s4 = 123+"";
String s5 = num + "";
4.3 与字符数组之间的转换
-
String --> char[]:调用String的toCharArray()
-
char[] --> String:调用String的构造器
String str1 = "abc123";
char[] charAray = str1.toCharArray();
for (char c : charAray) {
System.out.print(c+" ");
}
System.out.println();
char[] arr = {'h', 'e', 'l', 'l', 'o'};
String string = new String(arr); //调用String的构造器
System.out.println(string);
//输出
a b c 1 2 3
hello
4.4 与字节数组之间的转换
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器
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 s = new String(bytes);//使用默认的字符集,进行解码
System.out.println(s);
String s1 = new String(gbks);//解码与编码使用的字符集不一样
System.out.println(s1); //乱码
String gbk = new String(gbks, "gbk");
System.out.println(gbk);
//输出
[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
[97, 98, 99, 49, 50, 51, -42, -48, -71, -6]
abc123中国
abc123�й�
abc123中国
编码:字符串 -->字节 (看得懂 —>看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 —> 看得懂
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码
4.5 与StringBuffer、StringBuilder之间的转换
- String → StringBuffer、StringBuilder: 调用StringBuffer、StringBuilder构造器;
String str = "change";
StringBuffer stringBuffer = new StringBuffer(str);
StringBuilder stringBuilder = new StringBuilder(str);
-
StringBuffer、StringBuilder → String
-
调用String构造器;
-
StringBuffer、StringBuilder的toString();
-
StringBuffer stringBuffer = new StringBuffer("aa");
StringBuilder stringBuilder = new StringBuilder("bb");
//调用String构造器;
String str1 = new String(stringBuffer);
String str2 = new String(stringBuilder);
//StringBuffer、StringBuilder的toString();
String str3 = stringBuffer.toString();
String str4 = stringBuilder.toString();
二、StringBuffer和StringBuilder
1、StringBuffer类
java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。 很多方法与 String相同; 作为参数传递时,方法内部可以改变值;
1.1 构造器
StringBuffer类不同于 String,其对象必须使用构造器生成;
- StringBuffer():初始容量为16的字符串缓冲区;
- StringBuffer(int size):构造指定容量的字符串缓冲区;
- StringBuffer(String str):将内容初始化为指定字符串内容;
1.2 常用方法
- StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接;
StringBuffer s1 = new StringBuffer("abc");
s1.append(1);
s1.append('1');
System.out.println(s1); //输出abc11
-
StringBuffer delete(int start,int end):删除指定位置的内容;
-
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str;
-
StringBuffer insert(int offset, xxx):在指定位置插入xxx;
-
StringBuffer reverse() :把当前字符序列逆转;
当 append和insert时,如果原来vaue数组长度不够,可扩容。 如上这些方法支持方法链操作;
StringBuffer str = new StringBuffer();
//返回值都一样,并且是在原先的str上进行修改;可以使用方法链;
str.append("aa").append("bb").reverse;
System.out.println(str); //输出bbaa
方法链的原理(源代码):
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
【总结】
增:append(xxx) ;
删:delete(int start,int end) ;
改:setCharAt(int n ,char ch) / replace(int start, int end, String str) ;
查:charAt(int n ) ;
插:insert(int offset, xxx) ;
长度:length();
遍历:for() + charAt() / toString();
2、StringBuilder类
StringBuilder和 StringBuffer非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样,只是StringBuilder类没有加线程锁,执行效率更高;
3、String、StringBuffer、StringBuilder三者的对比
-
String:不可变的字符序列;底层使用char[]存储;占用内存(会不断的创建和回收对象)
-
StringBuffer:可变的字符序列;线程安全的,效率低;线程安全;底层使用char[]存储;
-
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;线程不安全;底层使用char[]存储
-
作为参数传递,方法内部String不会改变其值, StringBuffer和 StringBuilder会改变其值;
【String、StringBuffer、StringBuilder的执行效率】
从高到低排列:StringBuilder > StringBuffer > String
4、StringBuffer与StringBuilder的内存解析
【以StringBuffer为例】
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的char数组。
//查看长度
//输出0,这个lenth()并不是char[]的lenth();
System.out.println(sb1.length());
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
【StringBuffer构造器源码】
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
【扩容问题】
- 如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组;
- 默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中;
- 开发中建议使用:StringBuffer(int capacity) 或 StringBuilder(int capacity);