简介
/*
* java.lang.String:字符串类,并且该类加final修饰
*   底层是char数组 private final char value[];
*   字符串中很多特性与数组一致
*   1 字符串一旦创建不可更改
*   2 为了提升字符串的访问效率,Java提出字符串常量池,相当于一个缓冲区。存在于方法区中
*       引用类型对象保存在堆内存,字符串保存在静态区的字符串常量池中
*   3 在程序执行过程中,如果用到某个字符串,如“abc”,虚拟机会先去常量池中搜索,
*       如果存在,直接指向该字符串,否则新建一个字符串对象,并指向
*
* */

public class _01_StringB {

    public static void main(String[] args) {

        String s1 = "abc"; // 使用常量池,相当于简写了new
        String s2 = "abc";
        String s3 = "Abc";
        // true,==基本类型比较值,引用类型比较内存地址
        // s2和s3 指向地址相同
        // 只创建了两个对象“abc”和“Abc”
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
        System.out.println(s1.equals(s3));
        System.out.println("=========");

        s1 = "a2";
        // 这里只是新建了一个字符串“a2”,并让s1指向“a2”
        System.out.println(s1);
        System.out.println("===========");

        // 通过new创建字符串对象,对象地址保存在堆内存中,每使用new一次,就在堆中开辟一块空间
        // 因此s4和s5分别指向堆内存保存的对象,而对于字符串对象,堆内存中保存的是常量池中"123"的地址
        // 当堆内存指向常量池中“123”时,如果常量池中已存在对象“123”,则直接指向。否则在常量池中创建对象“123”,并指向
        // 堆内存中地址指向常量池。好比是将常量池中"123"分贝拷贝了一份放入堆内存中
        // 字符串日常使用时:不使用new创建
        String s4 = new String("123");
        String s5 = new String("123");
        // s4、s5创建了三个对象(堆内存两个、常量池一个),占用了五块空间(栈内存两个、堆内存两个、常量池一个)
        // == 比较的是地址,new了两次,地址自然就不一样
        System.out.println(s4 == s5);
        // 在string类中 已经重写了equals方法,重写后比较的是 值 不是地址
        System.out.println(s4.equals(s5));
    }
}

/*
* String一旦创建不可更改,使用时不要频繁拼接字符串,效率低,浪费空间,垃圾回收也存在问题
* */
public class _02_String {

    public static void main(String[] args) {
        String[] strs = {"a","b","c","d"};
        String temp = "";

        for (String str : strs) {
            temp += str;
        }
        System.out.println(temp);
        // a,b,c,d,"",""ab,""abc,""abcd,
    }
}

构造方法
/*
* String中常用构造方法
*   创建String对象的方式
* */
public class _03_StringC {

    public static void main(String[] args) {
        // 1
        String s1 = "123";
        // 2
        String s2 = new String("123");
        // 3 字节数组
        // 补充:数组声明
        // byte[] a = {1,2,3}; 静态声明,知道具体数组值
        // byte[] a = new byte[]{1,2,3}; 静态声明,多new
        // byte[] a = new byte[3]; 动态声明
        byte[] a = {97,98,99};
        String s3 = new String(a);
        // abc: 把数字转换为char 97->a 98->b 99->c
        // String底层是char数组
        String s4 = new String(new byte[]{97,98,99});
        System.out.println(s3 + " " + s4);


        // 4 字节数组,截取一部分,从下标1开始,去两个
        String s5 = new String(a,1,2);
        System.out.println(s5);
        // 5 无参构造创建
        String s6 = new String();
        System.out.println("空字符串" + s6);

        // 6 字符数组
        char[] chars = {'a','b','c','d'};
        String s7 = new String(chars);
        String s77 = new String(new char[]{'a','b','c'});
        System.out.println(s7);
        System.out.println(s77);

        // 7 字符数组截取
        String s8 = new String(chars,2,2);
        System.out.println(s8);

        // byte[] a = new byte[]{1,2,3}; 静态声明,多new。用于方法传参
        byte[] arr = {1,2,3};
        test(arr); // 显示静态声明,直接传参
        test(new byte[]{1,2,3}); // 匿名静态传参,不用额外声明一个数组对象,没保存,只能用一次
    }

    // byte[] a = new byte[]{1,2,3}; 方法传参用这种方式(应用场景)
    public static void test(byte[] arr){

    }
}

常用方法
/*
* String中常用方法
*   1 方法属于哪个类
*   2 什么方法,静态还是成员,如何调用
*   3 方法名,出参入参
*   4 方法功能
* */
public class _04_StringM {

    public static void main(String[] args) {

        // 1 int length(): 返回字符串长度
        String s1 = "abcdef!";
        System.out.println(s1.length());

        // 2 char charAt(int index): 返回指定位置上的字符
        char c1 = s1.charAt(6);
        System.out.println(c1);
        System.out.println("-------------");

        // 3 boolean endsWith(String suffix): 判断字符串是否以指定字符串结尾
        // boolean startsWith(String prefix): 判断字符串是否以指定这个词开头
        System.out.println(s1.endsWith("ef!")); // true
        System.out.println(s1.endsWith("ef! ")); // false 末尾有空格
        System.out.println("-------------");

        // 4 boolean equalsIgnoreCase(String anotherString):不区分大小写比较
        String s2 = "abc";
        System.out.println(s2.equalsIgnoreCase("AbC"));
        System.out.println(s2.equals("AbC")); // false

        // 5 byte[] getByte(): 把字符串转换为字节数组
        byte[] byteArr = s2.getBytes(StandardCharsets.UTF_8);
        for (byte b : byteArr) {
            System.out.println(b);
        }
        System.out.println("-------------");

        // 6 int indexOf(String str): 返回指定字符串的起始索引值,找不到返回-1,若有多个,返回第一个结束
        System.out.println(s1.indexOf("de"));

        // 7 int indexOf(String str, int fromIndex): 从指定位置开始找(包含),同方法6
        System.out.println("abcdefgabcd".indexOf("abc",1));
        System.out.println("-------------");

        // 8 int lastIndex(String str): 返回最后一次出现的位置,找不到返回-1(倒着遍历,第一次出现则为最后一次)
        // int lastIndexOf(String str, int fromIndex): 从指定位置开始找(包含),从指定位置反向搜索,第一次出现位置
        System.out.println("abcdefgabcd".lastIndexOf("abc"));
        System.out.println("abcdefgabcd".lastIndexOf("abc",5));
        System.out.println("-------------");

        // 9 String replaceAll(String regex, String replacement): 把一个字符替换为指定字符串
        // 类似方法 replace 功能一样,不过replace不支持正则表达式
        System.out.println("123321".replaceAll("1","a")); // a2332a
        // 会把 . 解析为正则表达式 而 . 代表任何字符
        System.out.println("1.2.3".replaceAll(".","-")); // ------
        // 正则表达式中, 可以通过 \ 把 . 转义为无意义字符,Java中 \ 是转义字符,所以要写 \\ 对 \ 进行转义
        System.out.println("1.2.3".replaceAll("\\.","-")); // 1-2-3
        // 不知此正则表达式,所以 . 就是 . 字符
        System.out.println("1.2.3".replace(".","-")); // 1-2-3
        System.out.println("-------------");

        // 10 String[] split(String regex): 分割字符串,通过指定分割符来分割字符串,返回分割后的新字符串数组,支持正则表达式
        String myTime = "2021-04-06";
        String[] myTimes = myTime.split("-");
        for (String time : myTimes) {
            System.out.println(time);
        }
        System.out.println("-------------");

        // 11 String substring(int begin): 获取该字符串从某个下标开始到结尾的字符串(包含)
        System.out.println("hello,world".substring(3));

        // 12 String substring(int beginIndex, int endIndex):
        // 获取该字符串从某个下标开始(包含),到从某个下标结束的字符串(不包含)
        System.out.println("hello,world".substring(3,6));

        // 13 char[] toCharArray(): 转换为字符数组
        char[] c2 = "hello,world".toCharArray();
        for (char c : c2) {
            System.out.print(c + " ");
        }
        System.out.println();
        System.out.println("-------------");

        // 14 String toUpperCase(): 转为大写
        System.out.println("dkjafbVDAb,x,xbAHDnn".toUpperCase(Locale.ROOT));
        // 15 String toLowerCase(): 转为小写
        System.out.println("dkjafbVDAb,x,xbAHDnn".toLowerCase(Locale.ROOT));

        // 16 String trim(): 删除字符串两边空格
        System.out.println("    ab   cd      ");
        System.out.println("    ab   cd      ".trim());

        // 17 static String valueOf(Object obj): 调用指定对象的toString方法,并且避免空指针异常
        // return (obj == null) ? "null" : obj.toString();
        _04_StringM s17 = null;
        System.out.println(s17);
        String s8 = String.valueOf(123);
        System.out.println(s8);

    }
}

StringBuffer和StringBuilder
/*
* java.lang.StringBuffer
* java.lang.StringBuilder
*
* 1 StringBuffer和StringBuilder是什么?
*   是一个可变长字符串缓冲区
* 2 原理
*   预先在内存中申请一块空间,可以容纳字符序列(字符数组)
*   如果预留空间不足,进行自动扩容
*   底层都是char[],默认初始化容量为16个字符
*
* 3 String、StringBuffer、StringBuilder区别
*   1 String是不可变字符序列 ,StringBuffer和StringBuilder是可变长字符序列
*   2 StringBuffer是线程安全,多线程环境下不会出现问题,因此效率低,常用于类中
*   3 StringBuilder是非线程安全,在多线程环境下可能出现问题,效率高,常用于方法中
* 4 如何选择StringBuffer和StringBuilder
*   多线程环境下,是否可能出现多个线程同时操作一个数据 的情况(增、删、改)
* */

public class _05_StringBu {

    public static void main(String[] args) {

        StringBuffer sbf = new StringBuffer();
        String[] str = {"a","b","c","d","e"};
        /*
        StringBuffer append源码
        public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);
            return this; // 返回this(即返回当前对象)可链式调用
        }
        * */
        for (String s : str) {
            sbf.append(s).append(".");
        }
        // 引用类型转String,调用toString方法,执行后变为非引用类型字符串
        String sb1 = sbf.toString();
        System.out.println(sb1);

        // StringBuffer类型也可直接在输出语句中打印,说明输出语句自动调用指定对象的toString方法
        /*
        println 输出 StringBuffer 引用类型 源码:
        public void println(Object x) {
            String s = String.valueOf(x); // 调用指定对象的toString方法
            synchronized (this) {
                print(s);
                newLine();
            }
        }
        * */
        System.out.println(sbf);

        StringBuilder sbd = new StringBuilder();
        String[] strs = {"a","b","c","d","e"};
        /*
        StringBuilder append源码
        public StringBuilder append(String str) {
            super.append(str);
            return this;
        }
        * */
        for (String s : strs) {
            sbd.append(s);
        }
        // 引用类型转String,调用toString方法
        System.out.println(sbd.reverse()); //反转
        String sb2 = sbd.toString();
        System.out.println(sb2);
        System.out.println(sbd);

    }
}

字符串比较易错点
/*
* String 不可任意拼接字符串
*   String s2 = "a" + "b";
*       这里,在编译阶段,会把 + 去掉
*       "a"+"b" "a"和"b"都是字面量,需要在编译阶段说明临时空间,需要通过值确定类型
*       编译时看到两个字符串相加,直接省略 + 拼接符,保存"ab"
*       故 s1 == s2 为true
*   String s3 = a+b;
*       这里 a和b都是变量,编译阶段不确定变量值
*       在运行阶段,两个字符串相加
*       会自动创建一个StringBuffer对象,然后把两个字符串拼接(StringBuffer的append方法)在一起
*       最终转换为String类型如下:(new)
*       public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
*       所以s3是指向堆内存。故 s1 == s3 为false
* */
public class _06_StringBu {

    public static void main(String[] args) {
        String s1 = "ab";
        String a = "a";
        String b = "b";
        String s2 = "a" + "b"; // 在常量池中有三个对象“a”,"b","ab"
        String s3 = a+b; // 变量相加
        String s4 = new String("ab");

        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false
        System.out.println(s1.equals(s3)); // true,值相等,在string类中 已经重写了equals方法,重写后比较的是 值 不是地址
        System.out.println(s1 == s4); // false
    }
}