Java的String常用方法及部分源码解析【读懂源码是关键】

1.字符与字符串

字符串内部包含一个字符数组,String 可以和 char[] 相互转换.

1. 字符数组转为字符串【new String()】

   private static void CharToString() {
    
    
        char[] value = {
    
    'a','b','c','d'};
        String str = new String(value);
        System.out.println(str);

        str = new String(value, 1, 2);// 从偏移量为 1 的位置开始,取 2 个字符,构建 String 对象
        System.out.println(str);
    }

abcd
bc

new String(字符数组)源码
在这里插入图片描述

new String(字符数组, 起始索引, 拷贝个数)
会调用Arrays.copyOf(数组, 长度)进行拷贝给一个新的数组

在这里插入图片描述

new String(“字符串”)源码
在这里插入图片描述
我们 ctrl+左键查看this.value
在这里插入图片描述

发现这些都是 String 类的成员数据
我们用的字符串的本质就是一个数组,由于被 final修饰,所以不可更改

2. 字符串转为字符数组【toCharArray&.charAt】

    private static void StringToChar(){
    
    
        String str = "hello";
        for (int i = 0; i < str.length(); i++) {
    
    
            System.out.print(str.charAt(i));
        }
		System.out.println();
        char[] chars = str.toCharArray();// 将字符串以字符数组的方式进行存储
        System.out.println(chars);
    }

hello
hello

在这里插入图片描述

charAt(索引)
如果索引越界就抛出异常否则就反悔对应的下标字符

思考:System.out.println(chars);
为什么字符数组可以直接打印呢?
我们按住 ctrl+鼠标左键进入源码查看
在这里插入图片描述
发现 char[] 数组可以像 double, String 这样的基础数据类型直接出书而不需要调用 Arrays.toString() 方法
而对于其它类型的数组则需要调用 Arrays.toString() 方法
在这里插入图片描述

2. 字节与字符串

1. 字节转换为字符串【new String()】

  private static void ByteToString(){
    
    
        byte[] bytes = {
    
    97, 98, 99, 100};
        String str = new String(bytes);
        System.out.println(str);
        str = new String(bytes, 1, 2);
        System.out.println(str);
        str = new String(bytes, 1);//弃用
        System.out.println(str);
  }

abcd
bc
šŢţŤ

分析new String(字节数组)源码
在这里插入图片描述

发现是当前类的一个重写方法,传入了一个(字节数组, 默认的0索引, 字节数组长度)

再点击 this 查看当前这个重写方法
在这里插入图片描述

我们发现了个新的方法StringCoding.decode(bytes, offset, length)

在进入函数 decode 中查看发现是一个带有 decode功能编码格式的StringCodeing 类,里边的其它函数来实现各种格式的编码【gbk, utf-8】
在这里插入图片描述

明文规定@Deprecated弃用的函数最好别用,否则实际使用中会出现意料之外的错误
在这里插入图片描述
方法被@Deprecated注解,说明是已经弃用的方法。所以最好不要用,一面如上述代码打印未知内容

2. 字符串转为字节数组【getBytes】

    private static void StringToByte(){
    
    
        String str = "abcdef";
        byte[] bytes = str.getBytes();
        System.out.println(Arrays.toString(bytes));

        str = "学习";
        try {
    
    
            bytes = str.getBytes("utf-8");// 1 个汉字 == 3个字节
            System.out.println(Arrays.toString(bytes));
            bytes = str.getBytes("gbk");// 1 个汉子 == 2 个字节
            System.out.println(Arrays.toString(bytes));
        } catch (UnsupportedEncodingException e) {
    
    
            e.printStackTrace();
        }
    }

str.getBytes()源码
在这里插入图片描述

当转换为字节数组的时候调用的是encode 方法而不是字节转换为字符串的时候decode方法

指定编码格式
str.getBytes(charsetName)源码
在这里插入图片描述
在点击 encode 查看
在这里插入图片描述

经过 if 条件判断完之后就会进行经过一些列的编码后就会return 出编码后的字节数组

3.小结

那么何时使用 byte[], 何时使用 char[] 呢?

  1. byte[] 是把 String 按照一个字节一个字节的方式处理, 这种适合在网络传输, 数据存储这样的场景下使用. 更适合 针对二进制数据来操作.
  2. char[] 是吧 String 按照一个字符一个字符的方式处理, 更适合针对文本数据来操作, 尤其是包含中文的时候.

4. 字符串常见操作

1. 字符串比较【equals, compareTo】

    private static void learn_equals(){
    
    
        String str = "hello";
        String str1 = new String("Hello");
        System.out.println(str == str1);// false
        System.out.println(str.equals(str1));//false
        System.out.println(str.equalsIgnoreCase(str1));//true
    }

false
false
true

equals()源码分析
在这里插入图片描述
equalsIgnoreCase()源码分析
在这里插入图片描述
我们查看一下regionMatches如何比较的
在这里插入图片描述
在这里插入图片描述

    private static void learn_compareTo(){
    
    
        String str = "AB";
        String str1 = "ABc";
        String str2 = "ABC";
        System.out.println(str.compareTo(str1));
        System.out.println(str.compareTo(str2));
    }

-1
-1

在这里插入图片描述

所以不要根据返回值来判断字母的ASCII的差
字符串的比较大小规则, 总结成三个字 “字典序” 相当于判定两个字符串在一本词典的前面还是后面. 先比较第一个字符的大小(根据 unicode 的值来判定), 如果不分胜负, 就依次比较后面的内容

2. 字符串查找【contains, indexOf】

   private static void learn_contains(){
    
    
        String str = "abcdefgh";
        System.out.println(str.contains(str));
        System.out.println(str.contains("cdef"));
        System.out.println(str.indexOf("c"));
        System.out.println(str.indexOf("abd"));
    }

true
true
2
-1

contains源码分析
在这里插入图片描述

我们发现 contains 函数是根据 indexOf 函数的返回值来进行判断的【因为 indexOf 函数找不到的时候会返回 -1

indexOf源码分析
在这里插入图片描述

发现是调用的 indexOf 方法,查找字符串 str 从 0 索引处开始匹配
进入 indexOf 中继续查看
在这里插入图片描述
发现还有一个 indexOf 方法,我们在此点进去查看
在这里插入图片描述

现在基本都是用contains()方法完成。
使用indexOf()需要注意的是,如果内容重复,它只能返回查找的第一个位置

3. 字符串替换【replace】

        String str = "ababcabcd";
        String str1 = str.replace('a', 'A');// 替换单个
        System.out.println(str1);
        String str2 = str.replace("ab", "A");// 替换多个
        System.out.println(str2);
        String str3 = str.replace("ab", "AB");// 替换多个
        System.out.println(str3);
        String str4 = str.replaceAll("ab", "AB");// 替换全部
        System.out.println(str4);
        String str5 = str.replaceFirst("ab", "AB");// 替换第一个
        System.out.println(str5);
    }

AbAbcAbcd
AAcAcd
ABABcABcd
ABABcABcd
ABabcabcd

由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串.
replace 的源码也未了解清楚

4. 字符串拆分【spilt】

private static void learn_split() {
    
    
        String str = "ab#abc#abcd";
        String[] arr1 = str.split("#");
        for (String s : arr1) {
    
    
            System.out.println(s);
        }
        System.out.println("=======");
        String[] arr2 = str.split("#", 2);
        for (String s : arr2) {
    
    
            System.out.println(s);
        }
        System.out.println("=======");
        String[] arr3 = str.split("#", 5);
        for (String s : arr3) {
    
    
            System.out.println(s);
        }

        System.out.println("拆分 IP 地址");
        String str1 = "192.168.0.1";
        String[] arr4 = str1.split("\\.");
        for (String s: arr4) {
    
    
            System.out.println(s);
        }

        System.out.println("多个分隔符");
        String str2 = "Java string-split#test";
        String[] arr5 = str2.split(" |-|#");
        for (String s: arr5) {
    
    
            System.out.println(s);
        }
        System.out.println("多次分割");
        String str3 = "name=zhangsan&pass=111";
        String[] arr6 = str3.split("&");
        for (int i = 0; i < arr6.length; i++) {
    
    
            String[] tmp = arr6[i].split("=");
            for (int j = 0; j < tmp.length; j++) {
    
    
                System.out.println(tmp[j]);
            }
        }
    }

ab
abc
abcd
=======
ab
abc#abcd
=======
ab
abc
abcd
拆分 IP 地址
192
168
0
1
多个分隔符
Java
string
split
test
多次分割
name
zhangsan
pass
111
  1. 字符 “|”, “*” , “+” 都得加上转义字符,前面加上"".
  2. 而如果是 “”,那么就得写成"\".
  3. 如果一个字符串中有多个分隔符,可以用 “|” 作为连字符.
  4. split的源码没有了解清楚

5. 字符串截取【substring】

  private static void learn_substring(){
    
    
        String str = "hello world";
        String ret = str.substring(2);// 从 2 下标开始截取到结尾
        System.out.println(ret);

        String ret1 = str.substring(2, 6);//[2, 6): 2下标开始包含2,6下标结束不包含6
        System.out.println(ret1);
    }

llo world
llo 

在这里插入图片描述

  1. 索引从0开始
  2. 注意前闭后开区间的写法, substring(2, 6) 表示包含 0 号下标的字符, 不包含 6 号下标

6. 其他操作方法

    private static void learn_other(){
    
    
        String str = "  H e L l O   ";
        String ret = str.trim();// 去除左右两边但是不能去除中间
        System.out.println(ret);

        String ret1 = str.toLowerCase();
        String ret2 = str.toUpperCase();
        System.out.println(ret1);
        System.out.println(ret2);

        String ret3 = str.concat("W o R l D");
        System.out.println(ret3);
        System.out.println(str.length());
        System.out.println(str.isEmpty());
    }

H e L l O
  h e l l o   
  H E L L O   
  H e L l O   W o R l D
14
false

5. StringBuffer && StringBuilder

首先来回顾下String类的特点:
任何的字符串常量都是String对象,而且String的常量一旦声明不可改变,如果改变对象内容,改变的是其引用的指 向而已。

通常来讲String的操作比较简单,但是由于String的不可更改特性,为了方便字符串的修改,提供StringBuffer和 StringBuilder类。

StringBuffer 和 StringBuilder 大部分功能是相同的,我们主要介绍StringBuffer 在String中使用"+"来进行字符串连接,但是这个操作在StringBuffer类中需要更改为append()方法:

public synchronized StringBuffer append(各种数据类型 b)

1. 初始化

    private static void learn_StringBufferAnd_StringBuilder(){
    
    
        StringBuffer sb = new StringBuffer("hello");
        StringBuilder sb2 = new StringBuilder("hello");
        String s = "hello";
//        StringBuilder stringBuilder = "hello";// 不可以直接赋值
        s += " world";// 会产生两个对象:bit, s
        /*
        StringBuilder: 操作也一样并且包含 String 的所有方法
         */
        sb.append(" world");
        sb.append(1).append("synchronized");
        System.out.println(sb);
        sb.reverse();// 修改了源字符串
        System.out.println(sb);
    }

hello world1synchronized
dezinorhcnys1dlrow olleh

2. 字符串拼接对比String

public static void main(String[] args) {
    
    
        String str = "abc";
        str += "def";
        System.out.println(str);
        /*
        System.out.println("优化==========优化");
        str = "abc";
        StringBuilder stringBuilder = new StringBuilder();
        str = stringBuilder.append(str).append("def").toString();
        System.out.println(str);
        */
    }

查看反汇编
在这里插入图片描述
还原优化的代码

public static void main(String[] args) {
    
    
		str = "abc";
        StringBuilder stringBuilder = new StringBuilder();
        str = stringBuilder.append(str).append("def").toString();
        System.out.println(str);
}

在这里插入图片描述
优化的效果有多明显呢?
字符串拼接效率对比如下图
String使用 “+” 进行硬拼接
在这里插入图片描述
注意 goto 语句,执行代码有 6 行。如果拼接很多次的话会带来很多不必要的执行语句

StringBuilder 优化后的代码
在这里插入图片描述

**String和StringBuffer最大的区别在于: **

String的内容无法修改【每次的修改都会产生新的对象】
StringBuffer的内容可以修改。频繁修改字符串的 情况考虑使用StingBuffer【每次修改都在源对象上改动而不造成额外的空间浪费】

3. String 和 StringBuffer 区别

为了更好理解String和StringBuffer,我们来看这两个类的继承结构:

goto 语句执行了一句,每次循环只执行 1 一条语句,因此效率会增加

4. StringBuilder 和 StringBuffer的区别

查看源码得知 StringBuffer 的方法都是 synchronnized (多线程同步),所以是安全的;而 StringBuilder 是单线程的。
StringBuilder源码
在这里插入图片描述
StringBuffer源码
在这里插入图片描述

发现 StringBuffer 的每个重写方法都是被 synchronized 所修饰的方法,代表的是多线程同步,是安全的方法

String类 StringBuffer类
public final class String implements java.io.Serializable, Comparable, CharSequence public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence

可以发现两个类都是"CharSequence"接口的子类。这个接口描述的是一系列的字符集。所以字符串是字符集的子 类,如果以后看见CharSequence,最简单的联想就是字符串。

注意:String和StringBuffer类不能直接转换。如果要想互相转换.可以采用下规则:

  1. String变为StringBuffer: 利用StringBuffer的构造方法或 append 方法
  2. StringBuffer变为String: 调用toString()方法。

除了append()方法外,StringBuffer也有一些String类没油的方法

方法 函数原型
字符串反转 public synchronized StringBuffer reverse()
字符串删除 public synchronized StringBuffer delete(int start, int end)
字符串插入 public synchronized StringBuffer insert(int offset, 各种数据类型 b)

反转

		String str = "abcdef";
//        StringBuilder stringBuilder = str;// 不可以直接赋值
        StringBuilder stringBuilder = new StringBuilder(str);// 调用构造方法
        System.out.println(stringBuilder.reverse());

fedcba

删除

		String str = "abcdef";
//        StringBuilder stringBuilder = str;// 不可以直接赋值
        StringBuilder stringBuilder = new StringBuilder(str);// 调用构造方法
        stringBuilder.delete(1,2);// b
        System.out.println(stringBuilder);

acdef

插入

String str = "abcdef";
//        StringBuilder stringBuilder = str;// 不可以直接赋值
        StringBuilder stringBuilder = new StringBuilder(str);// 调用构造方法
        stringBuilder.delete(1,2).insert(2, "Z");// b
        System.out.println(stringBuilder);

acZdef

面试题:请解释String、StringBuffer、StringBuilder的区别:

  1. String 是不可变对象,StringBuilder 和 StringBuffer 的内容可以修改
  2. StringBuilder 和 StringBuffer 大部分功能类似的
  3. StringBuffer 采用线程同步处理是安全的;StringBuilder 未采用同步处理,属于不安全操作

猜你喜欢

转载自blog.csdn.net/weixin_45364220/article/details/120489078