【JAVA】深入解析Java String类:原理与常用方法
目录
前言
一:String类的重要性
二:String 原理介绍
2.1:字符串构造
2.2:字符串常量池
2.3:字符串内部存储
三:常用方法
3.1:String对象的比较
1:== 比较是否引用同一个对象
2:比较大小关系
3:int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较
3.2:字符串查找
3.3:转换
1:数值和字符串转换
2:把字符串转为数字
3:把字符串大小写转换
4:把字符串转为字符数组
3.4 :字符串替换
3.5:字符串拆分
3.6 :字符串截取
3.7:去除左右两边空格
3.8:intern方法
3.9 :字符串的不可变性
3.10 :字符串的可变
四:关于一道String面试题
五:总结
前言
String 字符串是啥:把一组字符,串到一起(按照顺序排列到一起)
java中通过“”就能表示一个字符串
双引号中可以表示任意字符。
一:String类的重要性
在C语言中已经涉及到字符串了,但是在C语言中要表示字符串只能使用字符数组或者字符指针,可以使用标准库提供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想,而字符串应用又非常广泛,因此Java语言专门提供了String类。
二:String 原理介绍
2.1:字符串构造
使用String(创建一个String变量)
public class Test1 { public static void main(String[] args) { //构造String对象 //1.直接通过字符串字面值创建 String str1 = "Hello";//字面值常量 //2.通过new 的方式来构造 String也是类 String str2 = new String("hello");//String也是标准库内置的类, //使用这个类,不需要import String这个类处于标准库的java.lang这个包中 //3.通过字符数组来构造String char[] chars={'h','e','l','l','o'};//不需要\0; String s3=new String(chars); } }
我们使用new的方式构造字符串,会出现多余的:
在Java中,字符串字面量(如 `"hello"`)会被存储在字符串常量池(String Pool)中,以实现字符串的重用和节省内存。而使用 `new` 关键字创建的字符串对象(如 `new String("hello")`),会强制在堆内存中创建一个新的字符串对象,即使字符串常量池中已经存在相同的字符串。
在你的代码中,`str1` 和 `str2` 虽然都存储了 “hello” 这个字符串,但由于使用 `new` 关键字的方式,`str2` 会指向堆内存中的一个新对象,而 `str1` 会指向字符串常量池中的对象。
因此,`String str2 = new String("hello");` 不是多余的代码,但通常情况下,直接使用字符串字面量来创建字符串对象更为常见和高效,除非你有特定的需求需要创建一个新的字符串对象。
总结:
- 如果目的是为了节省内存并利用字符串常量池,可以使用 `String str1 = "hello";`。
- 如果确实需要创建一个新的字符串对象,那么 `String str2 = new String("hello");` 是合适的。
我们new String 这样的作法,就是从操作系统这里申请内存,并且创建字符串对象
字符串常量池,是java的内部机制,jvm运行时就会自动进行构造,不需要程序员来关注,常量池也不仅仅是只有“字符串常量”还有一些其他的常量池。
2.2:字符串常量池
字符串常量池(String Pool)是Java运行时数据区的一个特殊存储区域,主要用于存储字符串字面量(String literals)。它的主要作用包括节省内存和提高性能。
2.3:字符串内部存储
示例一: public class Test2 { public static void main(String[] args) { String str1 = "abc"; String str2 = "abc"; //String也是引用类型 //使用==比较,是判定两个引用保存的地址是否相等(判断两个引用是否指向同一个对象) System.out.println(str == str1); } } //true
图片更有说服力:
说明:
str1在进行存储的时候,“abc”会先储存到字符串常量池中
当str2再次存储的时候,先会检查字符串常量池当中是否存在“abc”常量,如果存在,则将不再重复存储。
示例二: public class Test2 { public static void main(String[] args) { String str = new String("abc"); String str1 = new String("abc"); System.out.println(str == str1); } } //把abc这个常量的每个字符 分别拷贝到两个String对象中, // 这两个对象虽然内容相同,但是它们是两个不同的对象,所以打印结果为false。 //false
三:常用方法
3.1:String对象的比较
符串的比较是常见操作之一,比如:字符串排序。Java中总共提供了3种方式:
1:== 比较是否引用同一个对象
1)两个String引用,指向的对象是否是同一个
2)两个String引用对应的对象,包含的字符内容是否是相同(我们一般使用的是equals)
注意:对于内置类型, == 比较的是变量中的值;对于引用类型 == 比较的是引用中的地址。1:public class Test2 { public static void main(String[] args) { String str ="abc"; String str1 = "abc"; System.out.println(str == str1); } } //true
int a = 10; int b = 20; int c = 10; // 对于基本类型变量,== 比较两个变量中存储的值是否相同 System.out.println(a == b); // false System.out.println(a == c); // true
2:
public class Test3 { public static void main(String[] args) { String s1="Hello"; String s2=new String("Hello"); System.out.println(s1.equals(s2)); } }//大小写也不可以 //true
2:比较大小关系
数字比大小,很明确的规则,大就大,小就小
字符串比较大小,规则用三个字描述“字典序”public class Test4{ public static void main(String[] args) { String s1="abc"; String s2="def"; System.out.println(s1.compareTo(s2)); //返回结果<0 s1<s2 //返回结果>0 s1>s2 //返回结果==0 s1==s2 } } //-3 //用a的ascal码减d的
比较汉字没有意义。(实践中,进行汉字的字符串排序,通常使用“拼音序”或者“笔画序”)
java标准库不支持这两种做法(需要使用第三方库)
3:int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较
public class Test4{ public static void main(String[] args) { String s1="AAA"; String s2="aaa"; System.out.println(s1.compareToIgnoreCase(s2)); } }//0
3.2:字符串查找
public static void main(String[] args) { String s = "Hello"; System.out.println(s.charAt(0)); System.out.println(s.charAt(1)); System.out.println(s.charAt(2)); System.out.println(s.charAt(3)); System.out.println(s.charAt(4)); //超出范围 //Hello
System.out.println(s.charAt(5));
就会出现报错信息 数组越界
汉字也是可以使用charAt来表示的。
String s = "aaabbbcccaaabbbccc"; System.out.println(s.charAt(3)); // 'b' System.out.println(s.indexOf('c')); // 6 System.out.println(s.indexOf('c', 10)); // 15 指定某个位置开始查找 System.out.println(s.indexOf("bbb")); // 3 System.out.println(s.indexOf("bbb", 10)); // 12 System.out.println(s.lastIndexOf('c')); // 17 System.out.println(s.lastIndexOf('c', 10)); // 8 System.out.println(s.lastIndexOf("bbb")); // 12 System.out.println(s.lastIndexOf("bbb", 10)); // 3
这些都是查找字符常用的关键字。
3.3:转换
1:数值和字符串转换
int n=10; String s=String.valueOf(n); System.out.println(s);//10
别看打印出来的是10,别以为是整型,其实是字符串,可以做字符串的运算
2:把字符串转为数字
String s="10"; int n=Integer.parseInt(s); //也可以设成double类型 System.out.println(n); System.out.println(n+10); //10 //20
这就是转换了,打印出来的结果都不一样,第二行如果他是字符串,就是拼接了,所以打印出来的是20.
3:把字符串大小写转换
String s="HHHHHHH"; System.out.println(s.toLowerCase()); //转小写 String s1="hhhhhhh"; System.out.println(s1.toUpperCase());//转大写 //hhhhhhh //HHHHHHH
此处转换的关键字都是生成新的String 不会修改本来的String
String中所有方法都不修改本身。
其实是一个不可变对象,相当于常量。
4:把字符串转为字符数组
String s="你好"; //通过toCharArray()方法将字符串转换为字符数组 char[] chars=s.toCharArray(); for(char c:chars){ System.out.println(c); } byte[] bytes=s.getBytes(); //通过getBytes()方法将字符串转换为字节数组 for(byte b:bytes){ System.out.printf("%x\n",b); } } //你 //好 //e4 //bd //a0 //e5 //a5 //bd
中文的编码方式
java中一个汉字可以通过一个char来表示,一个char是占2个字节(使用了utf16这样的编码方式来表示汉字,此时一个汉字占两个字节),到底一个汉字占2个字节,还是3个字节 取决于针对汉字的编码方式,(直接把字符串转成字节数组,此时是使用utf8这样的编码方式来表达汉字的,这种情况下一个汉字占3个字节。)全世界的字符编码方式,非常非常多,但我们就重点掌握utf8.(utf8是变长编码:针对不同的语言文字,占据的长度不同,对于英文符号,还是一个字节,对于中文就是三个字节 其他语言符号,还有2个字节,4个字节的)
5:格式化字符串
构造格式化字符串
类似printf
利用format方法,就可以完成格式化创建字符串操作int s=10; System.out.printf("X=%d\n",s); String str=String.format("x=%d\n",s); System.out.println(str);
printf只能打印
而String.format构造出来的字符串,不仅仅用来打印,我们构造出来的的str可以做字符串做的所有操作
3.4 :字符串替换
就是把字符串中的一部分修改为其他东西。
String s = "小李你真帅"; String s2=s.replace("小","大"); System.out.println(s2); //大李你真帅 String s = "小李你真帅,小张你真帅"; String s2=s.replace("小","大"); System.out.println(s2); //大李你真帅,大张你真帅
replace是替换所有字符,替换的结果是一个新的字符串,但不会修改原来的字符串
双引号也可以替换字符串
使用replaceFist:
String s = "小李你真帅,小张你真帅"; String s2=s.replaceFirst("小","大"); System.out.println(s2); //大李你真帅,小张你真帅 只替换首个字符
注意:也可以替换多个字符 String s = "小李你真帅,小张你真帅"; String s2=s.replaceFirst("小","大大"); System.out.println(s2); //大大李你真帅,小张你真帅
3.5:字符串拆分
指定一个分隔符,把一个字符串拆分多个部分,得到一个字符串数组。
String s="c,c++,java,python,c#,javascript"; String[] arr=s.split(","); for(String str:arr){ System.out.println(str); } //c //c++ //java //python //c# //javascript 用,切割,遍历到,就切割。
特例: String s="c.c++.java.python.c#.javascript"; String[] arr=s.split("."); for(String str:arr){ System.out.println(str); }//但是.为什么不可以呢? 正则表达式 是一种特殊的字符串 会包含一些特殊的字符,这些特殊字符有特定的含义,描述了字符串的“一系列特征” 后续可以通过正则表达式这样的字符串,匹配其他的字符串 .就属于正则表达式就是特殊字符,我们要想使用原始的.作为分割符,就需要转义。 在正则中 可以通过\. 来表示原始的. //在java的字符常量中,要求\也要转义,所以这里用\\。 String s="c.c++.java.python.c#.javascript"; String[] arr=s.split("\\."); for(String str:arr){ System.out.println(str); } //c //c++ //java //python //c# //javascript
正则表达式 是一种特殊的字符串
会包含一些特殊的字符,这些特殊字符有特定的含义,描述了字符串的“一系列特征” 后续可以通过正则表达式这样的字符串,匹配其他的字符串
.就属于正则表达式就是特殊字符,我们要想使用原始的.作为分割符,就需要转义。
在正则中 可以通过\. 来表示原始的.
//在java的字符常量中,要求\也要转义,所以这里用\\。
3.6 :字符串截取
字符串取子串
String s = "HelloWorld"; System.out.println(s.substring(2));//从指定位置,截取到末尾 System.out.println( s.substring(0, 5)); //截取区间【0,5), //llo World //Hello
注意事项:
1. 索引从0开始
2. 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
3. 虽然要求下标得在合理范围,但是10特殊,取子串并不访问10这个下标的元素。但是写更大就不可以,就会数组越界。
4.上述操作不会修改s字符串本身,都是生成新的字符串
3.7:去除左右两边空格
trim 会去掉字符串开头和结尾的空白字符(空格, 换行, 制表符 翻页 垂直等)
只有两侧,中间的不影响。
String s = " \f hello \n \t "; String s1=s.trim(); System.out.println(s1);; //hello(开头结尾没有空格)
两侧的空格,一般认为不影响文本内容理解,一般这样的空格都是为了排版方便。
3.8:intern方法
手动把字符串添加到常量池中。
代码中写死的“hello”这种字符串
编译器编译期间就能都提出来,运行的时候直接把这些内容加到池子当中。不需要程序员干预
String s = new String("hello");//Hello在常量池,但是new String("Hello")在堆上 System.out.println(s =="hello");//false String s1=s.intern();//将new String("Hello")加入常量池 //此时s1的内容也是“hello”,但是是指向常量池的“hello” System.out.println(s1 =="hello");//true
3.9 :字符串的不可变性
String 这样的对象,就像常量一样,里面的包含的属性是不能修改的~~
我们创建的String s 中存放的是new String 的地址,创建的字符串 是来自常量池中,
我们可以随意给String s 赋值或者修改,但是new里面的内容是不能修改的。
String s="hello"; for(int i=0;i<100;i++){ s+=i; //先通过s和i拼接成一个新的字符串,修改s的引用指向这个新的字符串}System.out.println(s.toString());
3.10 :字符串的可变
java还提供了可变的字符串Stringbuilder,用法和String 非常相似,可以方便和String相互转换,
但是这个东西可以修改。StringBuilder stringBuilder= new StringBuilder("hello"); for(int i=0;i<100;i++){ stringBuilder.append(i); } System.out.println(stringBuilder.toString()); //hello0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
//类似于String的+=这个过程不会创建新的StringBuilder对象 都是针对同一个对象操作。
从上述例子可以看出:String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可以修改。频繁修改字符串的情况考虑使用StringBuilder。
注意:String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
StringBuilder变为String: 调用toString()方法
四:关于一道String面试题
面试题:
1. String、StringBuffer、StringBuilder的区别
String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
StringBuffer与StringBuilder大部分功能是相似的
StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
五:总结
本篇博客给大家讲述了String类在使用中的原理结构,以及我们写代码的一些常用的使用方法。如果有不足,请大家批评指正,感谢观看。❀❀❀