1.定义方式
常见的三种字符串构造
public class Test1 {
public static void main(String[] args) {
// 使用常量串构造
String str1 = "abc";
System.out.println(str1);
// 直接newString对象
String str2 = new String("ABC");
System.out.println(str2);
// 使用字符数组进行构造
char[] array = {'h','e','l','l','o'};
String str3 = new String(array);
System.out.println(str3);
}
}
以上三种构造方法的解释:
注意:
1.String是引用类型,内部并不存储字符串本身,在String类的实现源码中,String类实例变量如下:
public static void main(String[] args) {
// s1和s2引用的是不同对象 s1和s3引用的是同一对象
String s1 = new String("hello");
String s2 = new String("world");
String s3 = s1;
}
在内存中的存储形式:
2. 在Java中""引起来的也是String类型对象。
// 打印"hello"字符串(String对象)的长度
System.out.println("hello".length());
2.内存中的存储
字符串常量池
在JDK7 开始,字符串常量池被挪到堆里了,常量池可以说在堆内存中
规定:只要是双引号引起来的字符串常量,会存在一个字符串常量池当中。
存储逻辑:
1.先检查这个内存(字符串常量池)有没有这个字符串2.如果没有,存进去
3.如果有,就不重复存储了。取出现有的对象即可。
练习一
我们知道 == 比较的是地址,通过对str1,2,3的比较我们可以知道它们在内存中是如何存储的!
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);
String str3 = "hello";
System.out.println(str1 == str3);
}
这个结果说明 str1 和 str2存放的地址是不一样的, str1 和 str3 存放的地址是一样的。
- 常量池只放字符串常量,比如该例中的"hello"
- str2 new一个String对象,在堆上开辟内存,假设内存地址是222,在这个String 对象中,存在一个value[] 保存着 orginal传入的字符串,这个val ==“hello”,因为在字符串常量池中已经有了"hello",所以val 直接指向 常量池中的"hello".但是str2 指向的依然是 222在堆中的空间。
- str3 也等于"hello",他也准备把hello放在常量池当中.此时常量池中已经存在"hello",那么之后str3 在存放"hello"地址的时候,就指向的是常量池中原来hello的地址。
练习二
public static void main(String[] args) {
String str1 = "hello";
String str2 = "hel"+"lo";
System.out.println(str1==str2);
String str3 = new String("hel")+"lo";
System.out.println(str1==str3);
}
在内存中的存储 :
- str1 指向字符串常量池中的 “hello”
- str2 是"hel"与"lo" 组合而成的,常量在编译的时候就已经确定了,所以在编译时,已经被处理为"hello",所以也指向 常量池中的"hello"。所以str1=str2
str3 首先new 了一个String(“hel”)对象,在堆中开辟一块空间,这个对象中的"hel"同时存放在常量池中,之后又在常量池中开辟一块空间存放 “lo”。两块部分之间的"+",将 String 的对象 与常量池中的 "lo"结合在堆中再次开辟一块新的空间,这块内存中的val ==“hello”,str3指向的是合并之后的对象 ,地址为333
练习三
public static void func(String str,char[] array){
str = "abcdef";
array[0] = 'g';
}
public static void main(String[] args) {
String str1 = "hello";
char[] val = {'a'};
System.out.println(str1);
System.out.println(Arrays.toString(val));
func(str1,val);
System.out.println("=================");
System.out.println(str1);
System.out.println(Arrays.toString(val));
}
- str1 指向字符串常量区的"hello"
- val 作为数组引用,指向堆中开辟的数组空间
- str 作为函数的形参,接收str1实参的值,此时str指向常量区的”hello“,但是在方法的内部,str = “abcde”,在字符串常量区中有开辟一块"abcde"的内存,但因为是形参出了该方法体就会失效,所以并没有改变原来的值
- array 作为函数的形参,接收val 实参的值,此时array 指向堆中 开辟的数组空间,此时通过array 来改变数组元素的内容,最终 改变的也同样是val 实参的内容.
3.常见操作
(1)String对象的比较
1. ==比较是否引用同一个对象
public static void main(String[] args) {
//对于基本类型变量,==比较两个变量中存储的值是否相同
int a = 10;
int b = 20;
int c = 10;
System.out.println(a == b);
System.out.println(a == c);
// 对于引用类型变量,==比较两个引用变量引用的是否为同一个对象
String s0 = "abc";
String s1 = new String("abc");
String s2 = new String("abc");
//s1,s2的val指向的都是常量池中的"abc",但s1,s2指向的对象不同
System.out.println(s1 == s2);
}
2. boolean equals(Object anObject) 方法:按照字典序比较
字典序:字符大小的顺序
equals比较:String对象中的逐个字符
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("abc");
String s3 = new String("ABC");
System.out.println(s1.equals(s2));
//忽略大小写比较
System.out.println(s1.equalsIgnoreCase(s3));
}
3.compareTo比较
int compareTo(String s) 方法: 按照字典序进行比较
与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:
1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
2. 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("abc");
String s4 = new String("abcdef");
System.out.println(s1.compareTo(s2)); // 不同输出字符差值-1
System.out.println(s1.compareTo(s3)); // 相同输出 0
System.out.println(s1.compareTo(s4)); // 前k个字符完全相同,输出长度差值 -3
}
//忽略大小写比较compareToIgnoreCase
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("ac");
String s3 = new String("ABc");
String s4 = new String("abcdef");
System.out.println(s1.compareToIgnoreCase(s2)); // 不同输出字符差值-1
System.out.println(s1.compareToIgnoreCase(s3)); // 相同输出 0
System.out.println(s1.compareToIgnoreCase(s4)); // 前k个字符完全相同,输出长度差值 -3
}
(2)字符串的查找
public static void main(String[] args) {
String str = "abcdefgabccd";
System.out.println(str.charAt(2));//输出下标为2的字符c
System.out.println(str.indexOf('a'));//返回a第一次出现的位置。没有则返回-1
System.out.println(str.indexOf('a',3));//从3位置d开始找a第一次出现的位置,7
System.out.println(str.indexOf("cd"));//返回字符串“cd”第一次出现的位置,即2
System.out.println(str.indexOf("cd",5));//从下标为5的位置开始找返回字符串“cd”第一次出现的位置,即10
System.out.println(str.lastIndexOf('a'));//从后往前找a第一次出现的位置,10
System.out.println(str.indexOf('a',5));//从下标为5的位置后往前找a第一次出现的位置,7
System.out.println(str.lastIndexOf("cd"));//从后往前找“cd”第一次出现的位置,10
System.out.println(str.lastIndexOf("cd",5));//从下标为5的位置后往前找“cd”第一次出现的位置,2
}
(3)转化
1. 数值和字符串转化
public static void main(String[] args) {
//数字转字符串
String s1 = String.valueOf(1234);
String s2 = String.valueOf(12.34);
String s3 = String.valueOf(true);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
//字符串转数字
int data1 = Integer.parseInt("1234");
double data2 = Double.parseDouble("12.34");
System.out.println(data1);
System.out.println(data2);
}
2.大小写转换
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO";
// 小写转大写
System.out.println(s1.toUpperCase());
//大写转小写
System.out.println(s2.toLowerCase());
}
3. 字符串转数组
public static void main(String[] args) {
//字符串转数组
String str1="ABCD";
char[]array=str1.toCharArray();
System.out.println(Arrays.toString(array));
//数组转字符串
char[] ch = {'a','b','c'};
String str2 = new String(ch);
System.out.println(str2);
}
4.格式化
public static void main(String[] args) {
String s=String.format("%d-%d-%d",2024,4,17);
System.out.println(s);
}
(4)字符串的替换
public static void main(String[] args) {
String s = "hello";
System.out.println(s.replaceAll("l","a"));
//heaao
System.out.println(s.replaceFirst("l", "a"));
//healo
}
(5)字符串拆分
1.字符串的整体拆分
public static void main(String[] args) {
String s = "aa bb cc dd";
String[] result = s.split(" ");
for (String str:result) {
System.out.println(str);
}
}
2.字符串的部分拆分
String[] result1 = s.split(" ",2);
for (String str1:result1) {
System.out.println(str1);
}
拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义.
3.IP地址的拆分
- “ \. ”才能表示一个真正的 “.”
- 同时"\"也需要进行转义,那么就又要再加一个斜杠。
- “\\.”这时字符串才只能被 “ . ”分割。
String str = "192.168.1.1" ;
String[] result2 = str.split("\\.") ;
for(String str2: result2) {
System.out.println(str2);
}
注意事项:
1. 字符"|","*","+"都得加上转义字符,前面加上 "\\" .
2. 而如果是 "\" ,那么就得写成 "\\\\" .
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符.
public static void main(String[] args) {
String s = "111@qq.com";
String[] result = s.split("@|\\.");
for (String str:result) {
System.out.println(str);
}
}
(6)字符串的截取
public static void main(String[] args) {
String str = "helloworld" ;
System.out.println(str.substring(5));
System.out.println(str.substring(0, 5));//[0,5)
}
注意事项:
1. 索引从0开始
2. 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
(7)其它操作方法
public static void main(String[] args) {
String str = " hello world ";
System.out.println(str.trim());
String str1 = "HELLO WORLD";
String str2 = "hello world";
System.out.println(str1.toLowerCase());
System.out.println(str1.toUpperCase());
}
4.字符串的特性
(1)不可变性
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
1. String类在设计时就是不可改变的,String类实现描述中已经说明了
JDK1.8中
String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:
1. String类被final修饰,表明该类不能被继承
2. value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中 的内容可以修改。
3.又因为value被private封装了,源码中有没有get和set方法,使得字符串具有不可变性。
所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,
但是其引用对象中的内容是可以修改的。
public static void main(String[] args) {
final int[]array=new int[]{1,3,4};
// array=new int[]{1,3,6};//报错
array[0]=99;//可以修改
System.out.println(Arrays.toString(array));
}
(2)字符串的修改
public static void main(String[] args) {
String str = "hello";
str += " world";
System.out.println(str); // 输出:hello world
}
因此:尽量避免对String的直接需要
如果要修改建议尽量 使用StringBuffer或者StringBuilder。
5.StringBuffer 和 StringBuilder
(1)常用方法
public static void main(String[] args) {
// 追加:即尾插-->字符、字符串、整形数字
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = sb1.append("world");
System.out.println(sb2);
System.out.println(sb1);
System.out.println(sb1 == sb2);
}
String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可 以修改。频繁修改字符串的情况考虑使用StringBuilder。
注意:String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
StringBuilder变为String: 调用toString()方法。
(2)区别
String 和 StringBuilder 及 StringBuffer 的区别
- String 进行拼接时,底层会被优化为StringBuilder
- String的拼接会产生临时对象,但是后两者每次都只是返回当前对象的引用。
- String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
StringBuffer与StringBuilder大部分功能是相似的
StringBuffer 和 StringBuilder 的区别主要体现在线程安全上 。
该关键字相当于一把锁来保证线程的安全
StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作