Java基础——彻底掌握String类
Posted 哈哈怪....
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础——彻底掌握String类相关的知识,希望对你有一定的参考价值。
前言:Java中有三大特殊类需要我们系统掌握,分别是String类,Object类以及包装类,这里,我们主讲String类,彻底掌握String类的使用
目录
6.1 String 类与StringBuilder类的相互转化
7. String类型和char[ ] 类型、byte[ ]类型的相互转化
1. 常见的创建字符串的三种方式
// 直接赋值法
String str1 = "hello";
// 使用关键字new
String str2 = new String("hello");
// 使用char[]
char[] data = 'h','e','l','l','o';
String str3 = new String(data);
由第三种创建方式我们可以知道,String内部仍是使用字符数组来存储元素的,下面是部分源码:
同时,由String类前面的final修饰符我们也可以知道,String类是不能再被继承修改的,这样可以保证所有使用JDK的程序员使用的是同一个String类
2. 字符串的内存布局
2.1 不同的内存空间
String是引用数据类型,故其存储的也是地址,在栈上保存,它所创建的对象则在堆上存储(这里,如果不懂栈空间、堆空间等可在上一篇Java中的类和对象这一篇中学习)
- 如使用直接赋值法,String str = "Hello"
内部存储如下:
- 而使用new关键字创建,则会创建不止一个对象,如这一句代码:
String str2 = new String("Hello")
这里,hello作为字符串字面常量是一个对象,而因为又使用了new关键字又开辟了一个空间,这个空间会把字符串字面量拷贝复制过来,故而,其实产生了两个对象,字符串字面量是一个垃圾空间
所以,一般由直接赋值法创建对象即可
2.2 字符串的比较
因为String是引用数据类型,存储的是地址,故而,我们可以得到字符串比较相等时,不可以直接使用 == 进行比较,而要使用equals()方法
(1)对于基本数据类型,== 比较的就是两个变量的值,但是对于引用数据类型,== 比较的其实是其存储的地址,所以不能直接用 == 比较
(2)equals的用法需注意,以下两种形式,建议第二种,尤其是当str1为用户输入时,如果用户未输入,那么str1默认为null,形式一的写法就有可能造成空指针异常
public static void main(String[] args)
String str1;
Scanner scanner = new Scanner(System.in);
str1 = scanner.next();
// 比较用户输入的字符串是否为“Hello”
// 形式一
System.out.println(str1.equals("Hello"));
// 形式二
System.out.println("Hello".equals(str1));
正常输入,两种形式都OK
但如果当str1为默认值Null时,形式一会出现空指针异常
3. 字符串的常量池
3.1 自动入池
当使用直接赋值法创建字符串时,JVM会对字符串创建一个字符串的常量池,常量池的主要目的就是为了保证效率和高复用
当使用直接赋值法(方式一)创建时,如果所创建的字符串字面值是第一次出现,JVM就会创建对象并将它扔入常量池,而如果该字面值不是第一次出现,JVM会直接从常量池中复用该对象
如下面这段代码:
public static void main(String[] args)
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
输出结果如下:
== 比较的是str1和str2所存储的地址,输出true说明str1,str2指向的是同一个字符串
其内部存储如下:(在创建str2时,Hello字面量已经存在,所以str2会直接指向而并不会在堆上再创健一个Hello)
3.2 手工入池
(1)当使用其他方法创建字符串时,并不会自动入池,如下面这段代码:
public static void main(String[] args)
String str1 = new String("Hello");
String str2 = "Hello";
System.out.println(str1 == str2);
输出结果:
这里输出的就是false了, 也验证了这种创建方法并不会入常量池
(2)这里我们就可以用intern()方法实现人工入池
public static void main(String[] args)
String str1 = new String("Hello").intern();
String str2 = "Hello";
System.out.println(str1 == str2);
输出结果:
4. 小结一
一般,就用直接赋值法创建字符串即可,然后用equals()方法比较值相等
5. 字符串的不可变性
5.1 为什么有不可变性
(1)由于String类内部并未提供getter方法,所以外部无法使用到存储字符串真实的char数组,所以,有关修改的操作都并不是真正的修改了原来的字符串,大都是通过创建一个新的字符串来达到看似修改的目的
(2)不可变的好处:
1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.
2. 不可变对象是线程安全的.
3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
5.2 验证不可变性
如下面这段代码,
public class StringPractice
public static void main(String[] args)
String str = "Hello";
change(str);
System.out.println(str);
public static void change(String s)
s = "Hi";
我们期望的输出结果是将str1的Hello变更为Hi,但我们来看真实的输出结果:
这就是因为字符串的不可变性,change 方法是新创建了一个字符串,并不是修改了原来的字符串,真实的内存如下图:
5.3 字符串的修改
(1)通常修改字符串的内容,我们选择使用 += 拼接,借助原字符串, 创建新的字符串,如下:
String str1 = "Hello";
str1 += "World";
System.out.println(str1);
但这里其实并不是真正的修改了原字符串,是通过创建新的字符串实现的,先创建了字面量World,然后 + 号创建了HelloWorld,最后 = 使str1指向了新的字符串HelloWorld,但是JVM没有那么笨拙,碰到 + 号,JVM会将字符串转为StringBuilder类
(2)要想真正修改原字符串,只能通过反射来破环封装,这里了解即可,不再延申
(3)通常,如果想大量修改字符串的内容,我们会用到StringBuffer类,和StringBuilder类,这两个类的区别在于,StringBuffer类是线程安全的,StringBuilder类是线程不安全的但效率要高
6. StringBuilder类
6.1 String 类与StringBuilder类的相互转化
(1)String -> StringBuilder
构造方法
append()方法,拼接扩展
(2)StringBuilder -> String
toString()方法
互相转化代码实现如下:
public class StringPractice
public static void main(String[] args)
String str1 = "Hello";
// String -> StringBuilder
// 构造方法
StringBuilder s1 = new StringBuilder(str1);
// append()方法
StringBuilder s2 = new StringBuilder("hello");
s2.append("world");
// StringBuilder -> String
String s3 = s2.toString();
6.2 StringBuilder类的其他方法
(1)reverse()方法,反转字符
(2)delete(int start,int end) 删除指定范围内的字符,左闭右开 ,start,end均为字母索引下标,从0开始数
(3)insert(int start,待插入数据) 将索引下标为start的位置插入数据
public static void main(String[] args)
StringBuilder s = new StringBuilder("hello");
// 反转字符
s.reverse();
// 输出 olleh
System.out.println(s);
// 删除
s.delete(1,3);
// 输出 oeh
System.out.println(s);
// 插入
s.insert(1,"zzzz");
// 输出 ozzzzeh
System.out.println(s);
结果:
7. String类型和char[ ] 类型、byte[ ]类型的相互转化
7.1 String类型和char[ ] 类型
(1)char[ ] -> String 即文章开篇写到的第三种创建字符串的方式
(2)String -> char[ ]
- charAt(int index) 方法,返回指定索引处的字符
- toCharArray() 方法,将字符串转变为字符数组
public static void main(String[] args)
String str = "Hello";
char a = str.charAt(1);
char b[] = str.toCharArray();
System.out.println(a);
System.out.println(b);
输出结果:
7.2 String类型和byte[ ]类型
(1)byte[ ] -> String 仍然用String的构造方法,可选择范围也可直接全部转换
(2)String -> byte[ ] getBytes()方法
public static void main(String[] args)
byte[] b = 'h','i';
// byte[ ] 全部转为String类型,构造
String str1 = new String(b);
// 从0开始长度为1的数组部分转变为String
String str2 = new String(b,0,1);
System.out.println(str1);
System.out.println(str2);
// String -> byte[]
byte[] b2 = str1.getBytes();
System.out.println(Arrays.toString(b2));
结果:
7.3 小结:
byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合针对二进制数据来操作.
char[] 是把 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候
8. String类的其他常用操作
8.1 字符串比较:
- equals()方法
- equalsIgnoreCase()方法,不区分大小写的比较
- compareTo()方法,与equals()方法不同的是,该方法返回的是整数:
1. 相等:返回0.
2. 小于:返回内容小于0.
3. 大于:返回内容大于0
(字符串的比较大小规则, 总结成三个字 "字典序" 相当于判定两个字符串在一本词典的前面还是后面. 先比较第一个字符的大小(根据 unicode 的值来判定), 如果不分胜负, 就依次比较后面的内容)
8.2 字符串查找
- contains(String s) 方法,判断子字符串是否存在
- indexOf(String s) 方法,从前向后查,返回s开始位置索引
- lastIndexOf(String s)方法,从后向前查,返回s开始位置索引
- startsWith(String s) 方法,判断是否以指定字符串开头
- endsWith(String s)方法,判断是否以指定字符串结尾
public static void main(String[] args)
String str = "HiHello";
String str2 = "H";
System.out.println(str.contains("H"));
System.out.println(str.indexOf("ll"));
System.out.println(str.lastIndexOf("ll"));
System.out.println(str.startsWith("Hi"));
System.out.println(str.endsWith("lo"));
输出结果:
true
4
4
true
true
8.3 字符串替换
- replaceAll(String regex,String replacement) 将所有的regex替换为replacement
- replaceFirst(String regex,String replacement) 仅将第一次遇到的regex替换为replacement
如下,注意区别:
public static void main(String[] args)
String str = "HiHello";
System.out.println(str.replaceAll("H","zzzz"));
System.out.println(str.replaceFirst("H","yyyy"));
结果:
zzzzizzzzello
yyyyiHello
8.4 字符串拆分
- public String[ ] split(String regex) 将字符串按regex拆分
- public String[ ] split(String regex,int limit) 将字符串按regex拆分,数组长度为limit
public static void main(String[] args)
String str = "Hi He llo";
// 以空格分割
String[] s = str.split(" ");
for(String a : s)
System.out.println(a);
// 以空格分割,数组长度为2
String[] s2 = str.split(" ",2);
for(String a : s2)
System.out.println(a);
Hi
He
llo
Hi
He llo
【注意】一些特殊的字符作为切割符可能无法区分,需要加上转义字符
1. 字符"|","*","+"都得加上转义字符,前面加上"\\"
2. 而如果是".",那么就得写成"\\\\."
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符
如:
public static void main(String[] args)
String str = "Hi.He.llo";
// 以空格分割
String[] s = str.split("\\\\.");
for(String a : s)
System.out.println(a);
8.5 字符串的截取
- subString(int beginIndex) 从beginIndex处开始截取到末尾
- subString(int beginIndex,int endIndex) 从beginIndex处开始截取到endIndex,左闭右开
8.6 其他方法
- str.length()获取长度
- str.isEmpty() 判断是否为空
- toUpperCase() 全部转大写
- toLowerCase() 全部转小写
- trim() 去掉字符串前后空格,而保留中间空格
public static void main(String[] args)
String str = "Hi.He.llo";
// 截取字符串
System.out.println(str.substring(1));
System.out.println(str.substring(1,4));
// 字符串长度
System.out.println(str.length());
// 判断是否为空
System.out.println(str.isEmpty());
// 转大写字母
System.out.println(str.toUpperCase());
// 转小写字母
System.out.println(str.toLowerCase());
输出结果:
i.He.llo
i.H
9
false
HI.HE.LLO
hi.he.llo
这就是String类的全部内容了,其中最核心的还是要能够理解常量池,以及字符串的不变性
以上是关于Java基础——彻底掌握String类的主要内容,如果未能解决你的问题,请参考以下文章
JAVA基础-常用类(DateStringBufferStringBuilderMathArrays和包装类)