String类
1、概述
String
声明为final
的,不可被继承
String
实现了Serializable
接口,表示字符串是支持序列化的(IO流的时候会提及)
String
实现了Comparable
接口,表示String可以比较大小
String
内部定义了final char[] value
,用于存储字符串数据
String
代表不可变的字符序列,简称:不可变性。
通过字面量的方式(区别于new
的方式)给一个字符串赋值,此时的字符串值声明在方法区内的字符串常量池当中
字符串常量池当中不会存储相同内容的字符串,注意是内容!!
2、不可变性
String
代表不可变的字符序列,简称:不可变性。
1、当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value
进项赋值
public class Test{
public static void main(String[] args) {
String s1 = "abc"; //字面量的定义方式
String s2 = "abc"; //字面量的定义方式
System.out.println(s1 == s2); //true
s1 = "hello";
System.out.println(s1 == s2); //false
System.out.println(s1); //hello
System.out.println(s2); //abc
}
}
内存图效果如下:
不会修改原有的值,而是新建赋值
2 、当对现有字符串进行连接操作时,也是需要重新指定内存区域赋值,不能使用原有的value
进项赋值
public class Test{
public static void main(String[] args) {
String s1 = "abc"; //字面量的定义方式
String s2 = s1 + "def"; //将a换成m
System.out.println(s1); //abc
System.out.println(s2); //abcdef
}
}
3 、当对调用String
的replace()
方法修改指定字符或字符串时,也是需要重新指定内存区域赋值,不能使用原有的value
进项赋值
public class Test{
public static void main(String[] args) {
String s1 = "abc"; //字面量的定义方式
String s2 = s1.replace('a','m'); //将a换成m
System.out.println(s1); //abc
System.out.println(s2); //mbc
}
}
2.1、不同实例化方式
字符串可以是通过字面量的方式赋值,也可以通过new
的方式创建
字符串常量存储在字符串常量池中的时候,就是共享的
public class Test{
public static void main(String[] args) {
String s1 = "abc"; //字面量的定义方式
String s2 = "abc"; //字面量的定义方式
String s3 = new String("abc"); //构造器的方式
String s4 = new String("abc"); //构造器的方式
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
System.out.println(s1 == s4); //false
System.out.println(s3 == s4); //false
}
}
内存图效果如下:
public class Test{
public static void main(String[] args) {
Person p1 = new Person("Jerry");
Person p2 = new Person("Jerry");
System.out.println(p1.name.equals(p2.name)); //true
System.out.println(p1.name == p2.name); //true
}
}
class Person{
String name;
public Person(String name){
this.name = name;
}
}
内存图效果如下:
面试题:
String s = new String("ab");
,此操作过后,在内存中创建了几个对象
答: 两个,一个是堆内存中的new
结构,一个是char[]
对应的常量池中的数据:“ab”
2.2、不同拼接操作的对比
1、常量与常量拼接结果是在常量池,且常量池中不会存在相同内容的常量
2、只要其中有一个是变量,结果就在堆内存中
3、如果拼接的结果调用intern()
方法,返回值就在常量池中
public class Test{
public static void main(String[] args) {
String s1 = "abc";
String s2 = "123";
String s3 = "abc123";
String s4 = "abc"+"123";
String s5 = s1 + "123";
//返回值得到的s6是常量池汇总已经存在的“abc123”
String s6 = (s1 + s2).intern();
System.out.println(s3 == s4); //true
System.out.println(s3 == s5); //false
System.out.println(s3 == s6); //true
System.out.println(s5 == s6); //false
}
}
内存图效果如下:
注意以下代码!!!
class Untitled {
public static void main(String[] args) {
String s1 = "abc123";
String s2 = "abc";
String s3 = s2 + "123";
System.out.println(s1 == s3); //false
final String s4 = "abc";
String s5 = s4 + "123";
System.out.println(s1 == s5); //true
}
}
此时的s4
是用final
修饰了的,所以属于常量,所以在s5
中,也是常量,存储在常量池中,所以与s1
相等
3、新增:参数传递
面试题:
以下程序运行结果:
class Untitled {
public static void main(String[] args) {
Untitled u = new Untitled();
u.change(u.str,u.c);
System.out.println(u.str);
System.out.println(u.c);
}
String str = new String("good");
char[] c = {'t','e','s','t'};
public void change(String s,char c[]){
s = "草莓";
c[0] = 'b';
}
}
执行结果如下
Java中没有真正的引用传递,只有值传递!
基本数据按值传递是传递的值的拷贝,引用数据按引用传递其实传递的是引用的地址值
注意: 字面量方式创建的String
类型数据的参数传递是和基本数据类型一致的,传递指的拷贝
1、 基本数据:按值传递是传递的值的拷贝
按值传递重要特点:
传递的是值的拷贝,也就是说传递后就互不相关了
public class TempTest {
public static void main(String[] args) {
TempTest t = new TempTest();
int a = 3;
t.test1(a);
//传递后,test1方法对变量值的改变不影响这里的a
System.out.println("main方法中的a=" + a);
}
private void test1(int a){
a = 5;
System.out.println("test1方法中的a=" + a);
}
}
执行结果如下
2、引用数据:按引用传递是传递的引用的地址值
按引用传递的重要特点:
传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)
class Untitled {
public static void main(String[] args) {
Untitled t = new Untitled();
//初始的age值为8
A a = new A();
//重新赋值为10
a.age = 10;
t.test1(a);
System.out.println("main方法中的age="+a.age);
}
private void test1(A a){
a.age = 20;
System.out.println("test1方法中的age="+a.age);
}
}
class A{
public int age = 8;
}
执行结果如下
内存分配示意图
调用方法时,传递的是变量a
的地址值
方法结束后,main
方法中的变量a
所存储的地址值未受到变化,但是方法执行后,里面的值却受到变化。
就相当于方法调用时,是将房间的钥匙(指地址值)copy了一份给方法test
,但是方法test
拿着钥匙动了房间里的东西(指对象里面的值)。main
方法中的变量a
还是拿着这把钥匙,钥匙并没有变化(指传递前和传递后都指向同一个引用)
3、对上述引用传递的改变
理解了上面的例子,可能有人会问,那么能不能让按照引用传递的值,相互不影响呢?就是test1方法里面的修改不影响到main方法里面呢?
方法是在test
方法里面新new
一个实例就可以了。只是在test
方法内新加了这一句a = new A();
class Untitled {
public static void main(String[] args) {
Untitled t = new Untitled();
//初始的age值为8
A a = new A();
//重新赋值为10
a.age = 10;
t.test1(a);
System.out.println("main方法中的age="+a.age);
}
private void test1(A a){
a = new A();
a.age = 20;
System.out.println("test1方法中的age="+a.age);
}
}
class A{
public int age = 8;
}
新new
一个实例之后,二者就互不干扰
4、常用方法
int length()
:返回字符串的长度
char charAt(索引)
:返回索引处的字符
boolean isEmpty()
:判断是否是空字符串
String toLowerCase()
:将String所有字符转为小写
String toUpperCase()
:将String所有字符转为大写
String trim()
:去除字符串前后空白(中间空白不管)
boolean equals(Object o)
:比较内容是否相同
boolean equalsIgnoreCase(String s)
:比较内容是否相同,忽略大小写
String concat()
:字符串连接,等价于“+”
int compareTo(String s)
:比较两个字符串大小
String substring(int beginindex)
:截取字符串,索引从beginindex到最后
String substring(int beginindex,int endindex)
:截取字符串,索引从beginindex到endindex,左闭右开
public class Test {
public static void main(String[] args) {
//length()
String str1 = "HEllo";
System.out.println(str1.length()); //5
//charAt()
System.out.println(str1.charAt(4)); //o
//isEmpty()
String str2 = "";
System.out.println(str2.isEmpty()); //true
//toLowerCase()、toUpperCase()
//此处并未改变str1的值,体现了String的不可变性
String str3 = str1.toLowerCase();
String str4 = str1.toUpperCase();
System.out.println(str1); //HEllo
System.out.println(str3); //hello
System.out.println(str4); //HELLO
//trim()
String str5 = " h h l l ";
System.out.println(str5.trim()); //h h l l
//equals()、equalsIgnoreCase()
String str6 = "hello";
System.out.println(str6.equals(str1)); //true
System.out.println(str6.equalsIgnoreCase(str1)); //false
//concat()
String str7 = str6.concat("hello");
System.out.println(str7); //hellohello
//compareTo()是将两个字符串进行相减操作
String str8 = "aaa";
String str9 = new String("aag");
System.out.println(str8.compareTo(str9)); //-6
//substring()包括索引5的字符到最后
String str10 = "hellohello";
System.out.println(str10.substring(5)); //hello
System.out.println(str10.substring(4,6)); //oh
}
}
boolean endsWith(String s)
:判断是否以指定字符串结尾
boolean startsWith(String s)
:判断是否以指定字符串开始
boolean startsWith(String s,int index)
:判断索引index的位置上是否以指定字符串开始
boolean contains(char s)
:当字符串包含指定char时,返回true
public class Test {
public static void main(String[] args) {
//endsWith()
String s1 = "hello";
System.out.println(s1.endsWith("lo")); //true
//startsWith()
System.out.println(s1.startsWith("He")); //false
System.out.println(s1.startsWith("ll",2)); //true
//contains()
String s2 = "LL";
System.out.println(s1.contains(s2)); //false
System.out.println(s1.contains("el")); //true
}
}
int indexOf(String s)
:返回指定字符串第一次出现的索引
int indexOf(String s,int index)
:返回指定字符串第一次出现的索引,从指定索引开始
int lastIndexOf(String s)
:返回指定字符串从右边数第一次出现的索引
int lastIndexOf(String s,int index)
:返回指定字符串从右边数第一次出现的索引,从指定索引开始
注意: 以上indexOf()方法和lastindexOf()方法若是未找到,返回的都是-1
class Untitled {
public static void main(String[] args) {
//indexOf()、lastindexOf()
String s1 = "helloworld";
System.out.println(s1.indexOf("lo")); //3
System.out.println(s1.indexOf("L")); //-1
System.out.println(s1.indexOf("l",3)); //3
System.out.println(s1.lastIndexOf("lo")); //3
System.out.println(s1.lastIndexOf("l")); //8
System.out.println(s1.lastIndexOf("l",4)); //3
}
}
String replace(旧的,新的)
:返回新的字符串,用新的替换了所有出现的旧的
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个。如果超过了,剩下的全部放到最后一个元素
5、String与其他类型的转换
5.1、与基本数据类型
字符串------->基本数据类型:调用包装类的parseXxx()
方法
基本数据类型------->字符串:调用String
静态方法valueOf()
方法。或者直接连接字符串
class Untitled {
public static void main(String[] args) {
String s1 = "123";
int i1 = Integer.parseInt(s1);
System.out.println(i1);
String s2 = String.valueOf(i1);
String s3 = i1 + "";
System.out.println(s1 == s2); //false
System.out.println(s1 == s3); //false
}
}
5.2、与数组char[ ]
字符串------->数组char[ ]:调用String
实例方法toCharArray()
方法
数组char[ ]------->字符串:调用String
的构造方法
class Untitled {
public static void main(String[] args) {
String s1 = "abc123";
char[] arr1 = s1.toCharArray();
//遍历数组
for(int i = 0;i < arr1.length; i ++){
System.out.print(arr1[i]);
}
System.out.println();
char[] arr2 = new char[]{'h','e','l','l','o'};
String s2 = new String(arr2);
//输出s2
System.out.println(s2);
}
}
5.3、与字节数组byte[ ]
编码过程:
字符串------->数组byte[ ]:调用String
实例方法getBytes()
方法
解码过程:
数组byte[ ]------->字符串:调用String
的构造方法(注意数组byte[ ]是使用哪种字符集)
说明:要求解码使用的字符集要与编码使用的字符集一致,否则会乱码
class Untitled {
public static void main(String[] args) {
String s1 = "abc123";
//使用默认字符集进行转换
byte[] arr1 = s1.getBytes();
//调用Arrays静态方法toString()
System.out.println(Arrays.toString(arr1));
//使用默认字符集进行转换
String s2 = new String(arr1);
//使用gbk字符集进行转换
byte[] arr2 = s1.getBytes("gbk");
System.out.println(Arrays.toString(arr2));
//使用gbk字符集进行转换
//String s2 = new String(arr2); 会出现乱码,编码集和解码集不一致
String s2 = new String(arr2,"gbk");
}
}
6、String、StringBuffer、StringBuilder
三者的异同:
- 底层都是使用
char[]
存储 String
:不可变的字符序列StringBuffer
:可变的字符序列,线程是安全的synchronized修饰,效率较低StringBuilder
:可变的字符序列,线程是不安全的,效率较高
对以上进行源码分析:
String
没有赋值的时候就没有给分配空间
String s1 = new String();
——//char[] value = new char[0];
赋值的时候也只会分配需要的字符串长度大小,不会多分配
String s2 = new String("abc");
——//char[] value = new char[]{'a','b','c'};
StringBuffer
没有赋值的时候底层创建了一个长度为16的数组
StringBuffer sb1 = new StringBuffer();
——//char[] value = new char[16];
sb1.append('a');
——//value[0] = 'a';
sb1.append('b');
——//value[1] = 'b';
赋值的时候底层创建了一个长度为字符串长度+16的数组
StringBuffer sb2 = new StringBuffer("abc");
——//char[] value = new char["abc".length()+16];
注意以下两个问题:
问题一
System.out.println(sb1.length());
// 0
System.out.println(sb2.length());
// 3
问题二
扩容问题:如果要添加的数据底层数组放不下,那就需要扩容底层的数组,默认情况下,扩容为原来容量的2倍+2,同时将原有数组中元素复制到新的数组中
对于此种情况,建议使用StringBuffer(int capacity)
,或者StringBuilder(int capacity)
。指定长度
7、新增:
面试题:
以下程序运行结果:
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length()); //4
System.out.println(sb); //"null"
StringBuffer sb1 = new StringBuffer(str);
System.out.println(sb); //报错:空指针异常
StringBuffer常用方法
StringBuffer append(xxx)
:字符串拼接
StringBuffer delete(int start,int end)
:删除指定索引位置内容,左闭右开
StringBuffer replace(int start,int end,String s)
:将指定位置替换成 s,左闭右开
StringBuffer insert(int index,xxx)
:指定位置插入
StringBuffer reverse()
:将字符串逆转
注意字符串拼接、替换等操作之后,字符串自身受到改变,改变的是自身,体现了可变性。
同时体现StringBuffer
中的以上方法支持方法链的操作,即可以sb1.append(1).append(1)
class Untitled {
public static void main(String[] args) {
//append()
StringBuffer sb1 = new StringBuffer("caomei");
sb1.append(1);
sb1.append(true).append("2"); //方法链操作
System.out.println(sb1); //caomei1true2
//delete()
sb1.delete(6,11);
System.out.println(sb1); //caomei2
//replace() 第3、4索引处的元素被替换
sb1.replace(3,5,"AAAA");
System.out.println(sb1); //caoAAAAi2
//insert()
System.out.println(sb1.insert(2,true)); //catrueoAAAAi2
//reverse()
System.out.println(sb1.reverse()); //2iAAAAoeurtac
}
}
int indexOf(String s)
:返回指定字符串第一次出现的索引
String substring(int start,int end)
: 返回的是字符串,不改变原来的字符串
int length()
:
char charAt(int i)
:返回索引处的字符
setCharAt(int i,char c)
:修改指定位置的一个字符
class Untitled {
public static void main(String[] args) {
StringBuffer sb1 = new StringBuffer("caomei");
//int indexOf(String s)
int i = sb1.indexOf("c");
System.out.println(i); //0
//String substring(int start,int end)
String s = sb1.substring(3,4);
System.out.println(s); //m
//int length()
int i1 = sb1.length()
System.out.println(i1);
//char charAt(int i)
char c = sb1.charAt(4);
System.out.println(c);
//setCharAt(int i,char c)
sb1.setCharAt(0,'b');
System.out.println(sb1);
}
}
8、算法练习题
需求:
将一个字符串指定部分进行翻转。比如 abcdefg 翻转为 abfedcg
需求:
获取一个字符串在了一个字符串中出现的次数。比如 ab 在 abkkcabvvabkkaavbbkab 中出现的次数
需求:
获取两个字符串中最大相同的子字符串。比如str1=“abcdefghellomnbv”; str
2=“cvxzhellouytr”;
提示:将短的字符串进行长度依次递减的子串与较长的子串比较
需求:
对字符串中字符进行自然顺序排序
提示:字符串变成字符串组。对数组排序、选择、冒泡,Arrays.sort()。将排序后的数组变成字符串
9、小结
String
概述String
声明为final
的,不可被继承String
实现了Serializable
接口,表示字符串是支持序列化的(IO流的时候会提及)String
实现了Comparable
接口,表示String可以比较大小String
内部定义了final char[] value
,用于存储字符串数据String
代表不可变的字符序列,简称:不可变性。
- 不可变性
- 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的
value
进项赋值 - 当对现有字符串进行连接操作时,也是需要重新指定内存区域赋值,不能使用原有的
value
进项赋值 - 当对调用
String
的replace()
方法修改指定字符或字符串时,也是需要重新指定内存区域赋值,不能使用原有的value
进项赋值
- 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的
- 字符串可以是通过字面量的方式赋值,也可以通过
new
的方式创建 - 不同拼接操作的对比
- 常量与常量拼接结果是在常量池,且常量池中不会存在相同内容的常量
- 只要其中有一个是变量,结果就在堆内存中
- 如果拼接的结果调用
intern()
方法,返回值就在常量池中
- 参数传递中,基本数据按值传递是传递的值的拷贝,引用数据按引用传递其实传递的是引用的地址值
- 常用方法
- 与其他类型的转换
- 基本数据类型
① 字符串—>基本数据类型:调用包装类的parseXxx()
方法
② 基本数据类型—>字符串:调用String
静态方法valueOf()
方法
或者直接连接字符串 - 数组char[ ]
①字符串—>数组char[ ]:调用String
实例方法toCharArray()
方法
② 数组char[ ]—>字符串:调用String
的构造方法 - 字节byte[ ]
①编码过程:字符串—>数组byte[ ]:调用String
实例方法getBytes()
方法
②解码过程:数组byte[ ]—>字符串:调用String
的构造方法(注意数组byte[ ]是使用哪种字符集)
③说明:要求解码使用的字符集要与编码使用的字符集一致,否则会乱码
- 基本数据类型
- String、StringBuffer、StringBuilder
- 三者的异同:
—>底层都是使用char[]
存储
①String
:不可变的字符序列
②StringBuffer
:可变的字符序列,线程是安全的synchronized饰,效率较低
③StringBuilder
:可变的字符序列,线程是不安全的,效率较高
④StringBuilder
最快,StringBuffer
其次,String
最慢
- 三者的异同:
- Date日期