Java基础——彻底掌握String类

前言:Java中有三大特殊类需要我们系统掌握,分别是String类,Object类以及包装类,这里,我们主讲String类,彻底掌握String类的使用

目录

1. 常见的创建字符串的三种方式

2. 字符串的内存布局

2.1 不同的内存空间

2.2 字符串的比较

3. 字符串的常量池

3.1 自动入池

 3.2 手工入池

 4. 小结一

5. 字符串的不可变性

5.1 为什么有不可变性

5.2 验证不可变性

5.3 字符串的修改

6. StringBuilder类

6.1 String 类与StringBuilder类的相互转化

6.2 StringBuilder类的其他方法

7. String类型和char[ ] 类型、byte[ ]类型的相互转化

7.1 String类型和char[ ] 类型

7.2 String类型和byte[ ]类型

 7.3 小结:

8. String类的其他常用操作 

8.1 字符串比较:

8.2 字符串查找

8.3 字符串替换

8.4 字符串拆分

8.5 字符串的截取

8.6 其他方法


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 intern()方法

 String.intern()是一个Native方法;

当通过语句str.intern()调用intern()方法后,JVM 就会在当前类的常量池中查找是否存在与str等值的String,若存在则直接返回常量池中相应Strnig的引用;若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。因此,只要是等值的String对象,使用intern()方法返回的都是常量池中同一个String引用,所以,这些等值的String对象通过intern()后使用==是可以匹配的

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类的全部内容了,其中最核心的还是要能够理解常量池,以及字符串的不变性 

  • 13
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨笨在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值