Java基础-String类【超详细讲解】
String类的特点
String在java.lang.String包中
1:特点
(1)String类型不能被继承,因为由final修饰
(2)String类型的对象是不可变:换句话说,只要修改字符串,就会产生新对象
package com.rh.bean;
import org.junit.Test;
public class Shipin07 {
@Test
public static void main(String[] args) {
// TODO Auto-generated method
// String s1="hello";
// s1="word";
// s1=s1+"java";
String s="";
change(s);
System.out.println(s);
}
public static void change(String str) {
str="hello";
}
}
只要字符串改变,例如拼接,就会产生新对象
(3)String对象不可变的特性,使得我们可以把一些字符串存到常量池中。字符串有常量池。常量池中的对象是可以共享的。
@Test
public void test02(){
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
}
返回true,这样可以节省些内存,常用的字符串可以保存起来,不用每次更新,也节省内存
**字符串常量池在哪里?**面试
以下存储情况为Oracle官方虚拟机HoySpot,不同虚拟机可能不同
常量的东西都存在方法区里。但字符串常量比较大,所以:
1:JDK1.6及以前:方法区
2:JDK1.7:挪到堆中,即在堆中单独划分了一块来存储字符串常量
3:JDK1.8:从堆中挪出,挪到一个“元空间meta space”,即类似 于方法区
补充:元空间meta space在内存中,甚至可以独立于java虚拟机,为了节省虚拟机的空间,因为字符串常量很多,且生命周期很长,会一直占用内存,所以挪出来
(4):String对象底层的存储
1:JDK1.9之前:底层是用char[]存储
2:JDK1.9之后:底层选用byte[]存储:因为byte数组更接近于二进制的处理
注意:
目前还是char[],所有的操作还是char数组来操作,目前使用的还是JDK1.8
(5)String对象怎么就不可变
1:底层char[]数组有final修饰,意味着这个数组不能扩容等,来达到存更多的字符(不能增加)
2:char[]数组是私有的,我们程序员无法直接操作这个char[]数组,而且String没有提供这样的方法来修改char[]数组的元素的值(不能修改)
String提供的所有的方法,对字符串的修改都是给你返回一个新的字符串对象
String对象的比较
字符串的比较
(1)==:比较对象的地址
String s1="hello";
String s2="hello";
System.out.print(s1==s2);
注意:只有两个字符串的常量对象比较才会返回true,其他的都是false
例如:
String s1=new String("hello");
String s2="hello";
System.out.print(s1==s2);
String s1=new String("hello");
String s2=new String("hello");
System.out.print(s1==s2);
(2)equals:比较字符串的内容(严格区分大小写):因为String类型重写了Object的equals
@Test
public void test04(){
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2));//true
String s3 = "hello";
System.out.println(s3.equals(s1));//true
String s4 = "Hello";
System.out.println(s3.equals(s4));//false
}
(3)equalsIgnoreCase(String anotherString):比较字符串的内容(不区分大小写)
@Test
public void test05(){
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equalsIgnoreCase(s2));//true
String s3 = "hello";
System.out.println(s3.equalsIgnoreCase(s1));//true
String s4 = "Hello";
System.out.println(s3.equalsIgnoreCase(s4));//true
}
(4)大小比较:严格区分大小写
String类型实现了Comparable接口,重写了compareTo(Object obj)方法,严格区分大小写
错误写法
/* if(s1 > s2){//不能直接使用比较运算符
}*/
应写为:
@Test
public void test06(){
String s1 = new String("hello");
String s2 = new String("helloworld");
/* if(s1 > s2){//不能直接使用比较运算符
}*/
if(s1.compareTo(s2) > 0){
System.out.println(s1 + ">" + s2);
}else if(s1.compareTo(s2) < 0){
System.out.println(s1 + "<" + s2);
}else{
System.out.println(s1 + "=" + s2);
}
}
依次比较对应位置的字符
hello和Hello,先[0]位置的h和H,h>H(ACSII码大32),直接认定为hello大于 Hello
hello和hella,先[0]、[1]、[2]、[3]比较,都一样,最后到[4] o>a,认定hello大于helloa
hello和helloword:前面都一样,长的大
补充:
对象的比较两种:
1:自然排序:
实现java.lang.Comparable接口,int compareTo(Object obj)
2:定制排序
(5)大小比较,不区分大小写
String 类型提供了一个方法compareToIgnoreCase,可以忽略大小写
@Test
public void test07(){
String s1 = new String("hello");
String s2 = new String("Hello");
if(s1.compareToIgnoreCase(s2) > 0){
System.out.println(s1 + ">" + s2);
}else if(s1.compareToIgnoreCase(s2) < 0){
System.out.println(s1 + "<" + s2);
}else{
System.out.println(s1 + "=" + s2);
}
}
用处:
@Test
public void test08(){
String[] arr = {"hello","chai","Java","Alice","Hi"};
//排序
//按照字母的顺序排列
Arrays.sort(arr);//按照元素的自然顺序排序
System.out.println(Arrays.toString(arr));
}
不区分大小写写法
@SuppressWarnings("all")
@Test
public void test09(){
String[] arr = {"hello","chai","Java","Alice","Hi"};
//排序
//按照字母的顺序排列,不区分大小写
Arrays.sort(arr,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
return s1.compareToIgnoreCase(s2);
}
});//按照元素的自然顺序排序
System.out.println(Arrays.toString(arr));
}
以上都是针对字母的,若是汉字
若是自然排序
@Test
public void test10(){
String[] arr = {"柴林燕","张三","李四","崔志恒","甄玉禄"};
Arrays.sort(arr);//按照自然顺序,按照每一个字符的Unicode编码值排序的
System.out.println(Arrays.toString(arr));
}
上图按Unicode编码排序,自然排序
若是要按拼音排序,用定制比较,所以
(6)按照每个国家的语言校队顺序
java.text.Collator,Collator类执行区分语言环境的String比较,使用此类可为自然语言文本构建搜索和排序例程。
Collator实现了Comparator接口
Collator是抽象类,不能直接创建对象,它有一个直接子类RuleBasedCollator
Collator内部提供了一个静态方法,可以获取一个它的子类对象
@Test
public void test11(){
String[] arr = {"柴林燕","张三","李四","崔志恒","甄玉禄"};
//希望按照拼音顺序,字典顺序
Arrays.sort(arr, Collator.getInstance());//默认语言环境,因为我现在的操作系统的平台是中文win
System.out.println(Arrays.toString(arr));
}
汉字有多音字,有时结果会不正确,大多数情况下是正确的,用的是哈希字典的排序规则,基本是按照拼音的顺序排序的
这里有个语言环境,上图为默认语言环境,因为我现在的操作系统的平台是中文的windows,所以默认按照,但若是平台是跨平台的效果,比如在国外的电脑上运行,还是想要中文的排序,可以写成如下:
public class TestString02 {
@Test
public void test12(){
String[] arr = {"柴林燕","张三","李四","崔志恒","甄玉禄"};
//希望按照拼音顺序,字典顺序
Arrays.sort(arr, Collator.getInstance(Locale.CHINA));//Locale.CHINA指定语言环境
System.out.println(Arrays.toString(arr));
}
可以指定语言环境
补充:
如图:Locale里有很多国家
String对象个数、拼接结果等
面试题:
1:对象个数
(1)String str=new String(“hello”);几个对象–》2个字符串个
@Test
public void test01(){
String str = "hello";//一个字符串对象
}
@Test
public void test02(){
String str = new String("hello");//两个字符串对象
//一个在常量池中:hello
//另一个在堆中,String的对象
//堆中的这个字符串对象char[]的value数组,指向常量池中"hello"的char[]的value
}
看源码:
value 就是chas[]数组,original就是hello
表示当前对象的value等于hello的value
不分那么细,就说常量池在方法区也没错
内存图:
(2)String str=new String(“hello”)
String str=new String(“hello”)
几个对象–》三个对象
常量池的hello共享的,new String分别指向一个对象
@Test
public void test03(){
String str1 = new String("hello");
String str2 = new String("hello");
//这两行代码,几个对象?3个
}
2:拼接的结果是在堆还是常量池中?
因为只有常量池才是共享,==比较才为true
(1)常量+常量在常量池
(2)常量+变量在堆
(3)变量+变量在堆
(4)xx.intern(),在常量池
@Test
public void test04(){
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4字符串内容也helloworld,s1是变量,"world"常量,变量 + 常量的结果在堆中
String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是变量,变量 + 变量的结果在堆中
String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//true
}
@Test
public void test05(){
final String s1 = "hello";
final String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4字符串内容也helloworld,s1是常量,"world"常量,常量+ 常量 结果在常量池中
String s5 = s1 + s2;//s5字符串内容也helloworld,s1和s2都是常量,常量+ 常量 结果在常量池中
String s6 = "hello" + "world";//常量+ 常量 结果在常量池中,因为编译期间就可以确定结果
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true
System.out.println(s3 == s6);//true
}
@Test
public void test06(){
String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = (s1 + "world").intern();//把拼接的结果放到常量池中
String s5 = (s1 + s2).intern();
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true
}
3:空字符串:三种
第一种:" "
第二种:new String()
第三种:new String(" ")
(1):
String s1 ;//局部变量未初始化,访问不了,无法使用,若是成员变量,默认值为null,可以访问
System.out.println(s1);报错,访问不了,无法使用
(2)
String s2=null;//初始化为null
System.out.println(s2.length());//编译能过,运行空指针异常
null没办法调用方法这些
(3)
String s3="";//空字符串常量对象
String s4=new String();//空字符串对象
String s5=new String("");//两个对象,一个常量池中的,一个堆中的
这三个都是字符串对象,都是空字符串
String s3="";//空字符串常量对象
String s4=new String();//空字符串对象
String s5=new String("");//两个对象,一个常量池中的,一个堆中的
System.out.println(s3.length());
System.out.println(s4.length());
System.out.println(s5.length());
如何判断空字符串?四种方式,推荐第三种
第一种方法:
有风险,若是test08方法里传入null,运行时会报空指针异常
应该为:先判断一下
第二种方法:
第三种方法:
和上一种写法相比,不用判断是否为空,推荐
因为“”.equals(str)中的" "是一个常量,常量一定为非空对象,顶多str为null,返回false而已,运行不会报空指针异常
规范:有常量和非常量比较的时候,将常量对象放在前面
第四种:
总结:怎么优化?第三种最好
String的常用方法(一)
方法系列一:
(1)int length():返回字符串的长度,返回的是字符的个数。因为String的内部(JDK1.9之前)用char[]实现,这个长度就是value数组的长度
(2)boolean isEmpty():是否是空字符串
(3)String toLowerCase():
String toUpperCase()
@Test
public void test1(){
String str = "hello";
System.out.println(str.toUpperCase());
}
只针对字母的大小写,将字符串中的大写字母(小写字母)转换为小写字母(大写字母),数字、汉字等没有
(4)String trim():去掉字符串的前后空白符
没去掉,原因:字符串任何修改都会产生新的对象,需要接收这个新对象
应该为:
另外定义一个变量接收也行,反正就是要先接收这个新对象才能使用新对象
用在什么地方?
经常当web页面向服务器端传输数据时,经常会对传输的数据trim()一下,因为用户可能在输入数据的时候,不小心在前面后面输入空格,用这个方法把它去掉,经常在判断的时候加入trim()
(5)String concat():拼接,等价于+
@Test
public void test3(){
String s1 = "hello";
String s2 = "world";
String s3 = s1 + s2;
String s4 = s1.concat(s2);
System.out.println(s3 == s4);//false
System.out.println(s3.equals(s4));//true
}
String的常用方法(二)
方法系列二:和char相关
(1)char[] toCharArray()
(2)char charAt(index)
(3)String(char[] arr)
将字符数组变为字符串
(4)String(char[] arr,int offset,int count)
将部分的字符数组变为字符串
String的常用方法(三):
方法系列三:和byte相关,或者说和编码与解码相关
**(1)
编码:byte[] getBytes():编码的方法,使用平台默认的字符编码进行编码的
byte[] getBytes(编码方法):可以设置编码方法
编码:
对于ASCII码范围内(0~127)无论用什么编码方式,结果都是一样的,一个字符对应一个字节的编码值
对于其他的字符,编码结果不一定是几个字节,例如汉字
UTF-8:变长的,但是大多数汉字都是3个字节
GBK、GB2312:固定的两个字节
ISO8859-1:不支持中文,所有字符都编为1个字节
编码:
把字符–》字节的过程,编给计算机用的
解码:
把字节–》字符的过程,解给人看的
变成编码值
以0开头的,一个字节,以110开头的,两个字节构成一个字符,以1110开头的,三个字节构成一个字符,以此类推
GBK、GB2312编
一个汉字两个字节
**ISO8859-1编**
ISO8859-1转不回来,因为之前编的时候截掉了一部分
不指定编码也不行
String string =new String(bytes);也一样转不回来
而GBK、utf-8可以
(2)解码方法
new String(字节数组)
new String(字节数组,编码方式)
乱码情况两种原因:
1:编码与解码字符集不一致
解不回来
2:缺字节
会丢掉一些字节,汉字至少两个字节
String的常用方法(四)
方法系列(4)
(1)boolean startsWith(xx)
(2) boolean endsWith(xx)
可以多个字
String的常用方法(五)
方法系列:和查找有关
(1)是否包含
boolean contains()
(2)int indexOf(xx)
告诉是第几个位置,如果存在返回下标,如果不存在返回-1
若是两个点,返回第一个点的位置
(3)int lastIndexOf(xx):
告诉最后一个xx是第几个位置,如果存在返回下标,如果不存在返回-1
String的常用方法(六)
方法系列6:和截取相关
(1)String substring(int beginIndex):从字符串的[beginIndex]截取到最后
(2) String substring(int beginIndex,int endIndex):
截取字符串的[beginIndex,endIndex)部分
String的常用方法(七)
方法系列7:匹配规则
e.g:判断一个字符串是否全是由数字组成,并且第一位不能是0,长度为9位
解决方法:可以转换为一个字符数组,一个一个的比较,但太麻烦,用matches方法简洁
matches(正则表达式)
正则表达式:用于检测文本的格式
校验某个字符串是否符合xx规则
例如:电话号码:甚至可以校验是不是移动号…
银行卡号
邮箱格式
…
String的常用方法(八)
方法系列8:替换
(1)String replace(target,value)
(2) String replaceAll(String regex,String replacement)
(3)String replaceFirs(String regex,String replacement)
其中2和3支持正则
String的常用方法(九)
方法系列9:拆分
String [] split(xx)
前面多了一个空字符串
解决方法:去掉位于前面和后面的数字
e.g:
有一个student类
拆分不对:因为|在正则中是有特殊意义,我这里要把它当成普通的|,用|,转一次为正则的转义,在java中\还要再转义一次,第一次转|,第二次转\,所以\|
面试中经常考|和.的转义