1. 创建字符串
常见的构造 String 的方式
        //方式一:
        String str = "hello world";
        //方式二:
        String str2 = new String("Hello world");
        //方式三:
        char[] array = {'a','b','c'};
        String str3 = new String(array);
 
注意事项:
- " hello " 这样的字符串字面值常量,类型也是 String.
 - String 也是引用类型, String str = " hello " ; 这样的代码内存布局如下

 
我们曾经在学习数组的时候就提到了引用的概念。
引用类似于C语言中的指针,只是在栈上开辟了一小块内存空间保存一个地址,但是引用和指针又不太相同,指针能进行各种数字运算,但是引用不能,这是一种" 没那么灵活的指针 “。
另外,也可以把引用想象成一个标签,可以” 贴 "到对象上,一个对象可以贴一个标签,也可以贴多个。如果一个对象上面一个标签也没有,那么这个对象就会被JVM当成垃圾回收掉。
由于String 是引用类型,因此对于以下代码
        String str1 = "hello";
        String str2 = str1;
 
内存布局如图:
 
那么有同学就会说,是不是修改 str1 , str2 也会随之变化呢?
        String str1 = "hello";
        String str2 = str1;
        str1 = "world";
        System.out.println(str2);
 
执行结果:
 
 我们发现," 修改 " str1 之后,str2 并没有发生变化,输出的结果仍然是 hello .这是为什么呢?
 事实上,str1 = " world " 这样的代码并不算" 修改 "字符串,而是让 str1 这个引用指向了一个新的String 对象。
 
2. 字符串比较相等
2.1 使用 == 比较
对于内置类型,== 比较的是变量中的值。
 代码一:
        int x = 10;
        int y = 10;
        System.out.println(x == y);
 
执行结果:
 
 对于引用类型,== 比较的是引用中的地址。
 代码二:
        String str1 = "hello";
        String str2 = "hello";
        System.out.println( str1 == str2);
 
执行结果:
 
 看起来貌似没什么问题,再换个代码试一下,就会发现意外的小惊喜!
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println( str1 == str2);
 
执行结果:
 
 我们来分析一下两种创建String 方式的差异。
 代码一内存布局:
 
 我们发现,str1 和 str2 是指向同一个对象的。此时如 " hello " 这样的字符串常量是在字符串常量池中的。
关于字符串常量池:
如" hello " 这样的字符串字面值常量,也是需要一定的内存空间来存储的,这样的常量有一个特点,就是不需要修改。所以如果代码中有多个地方引用都需要使用 " hello " 的话,就之间引用到常量池的这个我位置就行了,而没必要把 " hello " 在内存中存储多次。
代码二内存布局:
 
 我们可以得出结论:
 通过 String str1 = new String ( " hello" ) ; 这样的方式创建的String 对象相当于在堆上另外开辟了空间来存储 " hello " 的内容,也就是此时内存中存在两份 " hello " .
 String 使用 == 比较的并不是字符串中的内容,而是比较两个引用是否指向了同一个对象。
2.2 使用equals比较
Java中要想比较字符串中的内容,必须采用 String 类提供的 equals 方法。
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println( str1.equals(str2));
 
执行结果:
 
 equals 使用注意事项
 现在需要比较 str 和 " hello " 两个字符串是否相等,我们应该如何来写呢?
        String str = new String("hello");
        //方式一:
        System.out.println(str.equals("hello"));
        //方式二;
        System.out.println("hello".equals(str));
 
那么在上面的代码中,哪种方式更好呢?
 我们更推荐使用" 方式二 " ,一旦 str 是 null ,方式一 的代码会抛出异常,而方式二不会。
 注意事项:
 " hello " 这样的字面值常量,本质上也是一个 String 对象,完全可以使用 equals 等String 对象的方法。
2.3 使用compareTo 方法比较
与equals不同的是,equals返回的是boolean类型,而 compareTo 返回的是 int 类型。具体比较方式:
 1.先按照字典次序进行比较,如果出现不等的字符,直接返回两个字符的大小差值。
 2.如果前k个字符相等(k为两个字符串长度中的较小值),返回值为两个字符串的长度差值。
    public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = new String("ac");
        String s3 = new String("abc");
        String s4 = new String("abcdef");
        System.out.println(s1.compareTo(s2));
        System.out.println(s1.compareTo(s3));
        System.out.println(s1.compareTo(s4));
    }
 
解析:
- 遇到了不同的字符,输出字符差值-1.
 - 两个字符串相同,输出0.
 - 前k个字符完全相同,输出长度差值-3.
 
2.4使用 compareToIgnoreCase方法比较
注意:此方法与compareTo方法的不同之处在于 忽略大小写比较
    public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = new String("ac");
        String s3 = new String("ABc");
        String s4 = new String("abcdef");
        System.out.println(s1.compareToIgnoreCase(s2));
        System.out.println(s1.compareToIgnoreCase(s3));
        System.out.println(s1.compareToIgnoreCase(s4));
    }
 
所以,上述代码的运行结果,和之前的结果一样。
2.5 字符串查找
1.char charAt ( int index )
 功能:返回index位置上的字符,如果index为负数或者越界,抛出IndexOutOfBoundsException异常。
    public static void main(String[] args) {
        String str = "hello";
        char ch = str.charAt(2);
        System.out.println(ch);
    }
 
上述代码为获取字符串 “hello” 下标为2的字符,第一个字符的下标是0,那么依次往后,下标为2的字符就是 ’ l '.
 2. int indexOf ( int ch )
 功能:返回ch 第一次出现的位置,若字符串中没有ch, 那么返回-1.
    public static void main(String[] args) {
        String str = "hello";
        int index = str.indexOf("l");
        System.out.println(index);
    }
 
上述代码是获取字符串 “hello” 中 " l ",的下标,这里有一个点需要注意,字符串中有两个
 " l ",但是这个方法它获取的是第一次出现的位置下标,所以输出的结果是2.
 3. int indexOf ( int ch, int fromIndex )
 功能:从fromIndex 位置开始找ch 第一次出现的位置,没有就返回-1.
    public static void main(String[] args) {
        String str = "hello";
        int index = str.indexOf("l",3);
        System.out.println(index);
    }
 
上述代码是从第三个位置开始找字符 " l ",我们可以看到第三个位置正好就是字符 " l ",所以结果输出3。另外我们也注意到,这里是从第3个位置开始找,同时也是包括3这个位置的。
 4. int indexOf ( String str )
 功能:返回 str 第一次出现的位置,没有返回-1.
    public static void main(String[] args) {
        String str = "abcdefcd";
        int index = str.indexOf("cd");
        System.out.println(index);
    }
 
上述代码是求字符串 " cd " 在字符串 " abcdefcd " 中第一次出现的位置,输出的结果是2(输出的是子字符串中首个字符的下标)。
 5. int indexOf ( String str, int fromIndex )
 功能:从fromIndex位置开始找str第一次出现的位置,没有就返回-1。
    public static void main(String[] args) {
        String str = "abcdefcd";
        int index = str.indexOf("cd",5);
        System.out.println(index);
    }
 
上述代码是求字符串 " cd " 在字符串 " abcdefcd " 中从5下标开始第一次出现的位置,输出的结果是6(输出的是子字符串中首个字符的下标).
 6. int lastIndexOf ( int ch )
 功能:从后往前找,返回字符ch第一次出现的位置,没有则返回-1.
    public static void main(String[] args) {
        String str = "abcdefcd";
        int index = str.lastIndexOf("c");
        System.out.println(index);
    }
 
上述代码是求字符 " c " 在字符串 " abcdefcd " 中最后的位置开始第一次出现的位置,输出的结果是6(输出的是子字符串中首个字符的下标).
 7. int lastIndexOf ( int ch, int fromIndex)
 功能:从fromIndex位置开始找,返回字符ch 第一次出现的位置,没有就返回-1.
    public static void main(String[] args) {
        String str = "abcdabed";
        int index = str.lastIndexOf("c",1);
        System.out.println(index);
    }
 
上述代码是求字符串 " c " 在字符串 " abcdabed " 中下标为1的位置开始从后往前查找,由于这样一来刚好就错过了c出现的位置,所以输出的结果是-1.
 8. int lastIndexOf ( String str )
 功能:从后往前找,返回字符串str第一次出现的位置,没有则返回-1.
    public static void main(String[] args) {
        String str = "abcdefcd";
        int index = str.lastIndexOf("cd");
        System.out.println(index);
    }
 
上述代码是求字符串 " cd " 在字符串 " abcdefcd " 中最后的位置开始第一次出现的位置,输出的结果是6(输出的是子字符串中首个字符的下标).
 9. int lastIndexOf ( String str, int fromIndex )
 功能:从fromIndex位置开始找,从后往前找字符串 str 第一次出现的位置,没有则返回-1.
    public static void main(String[] args) {
        String str = "abcdabed";
        int index = str.lastIndexOf("cd",1);
        System.out.println(index);
    }
 
上述代码是求字符串 " cd " 在字符串 " abcdabed " 中下标为1的位置开始从后往前查找,由于这样一来刚好就错过了cd出现的位置,所以输出的结果是-1.
2.6 转化
1.数值和字符串转化
 1)数字转字符串
class Student{
    public String name;
    public int age;
    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
    public static void main(String[] args) {
        String s1 = String.valueOf(1234);
        String s2 = String.valueOf(12.34);
        String s3 = String.valueOf(true);
        String s4 = String.valueOf(new Student("翠花",2));
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
    }
 
运行结果:
 
s1是 int 向 String 的转化;
 s2是double 向 String 的转化;
 s3是boolean 向 String 的转化;
 s4是 引用类型向 String 的转化,由于在Student类中重写了toString 方法,所以这里可以正常输出。
 2. 字符串转数字
    public static void main(String[] args) {
        int num1 = Integer.parseInt("1234");
        double num2 = Double.parseDouble("12.34");
        System.out.println(num1);
        System.out.println(num2);
    }
 
上述代码分别是将字符串转为整型和double型,需要注意的一点是,在接收返回值的时候类型一定要匹配。
 3. 大小写转换
小写转大写:
    public static void main(String[] args) {
        String str1 = "hello";
        System.out.println(str1.toUpperCase());
    }
 
大写转小写:
    public static void main(String[] args) {
        String str1 = "HELLO";
        System.out.println(str1.toLowerCase());
    }
 
4. 字符串转数组
    public static void main(String[] args) {
        String str = "hello";
        char[] array = str.toCharArray();
        System.out.println(array[2]);//输出l
    }
 
数组转字符串:
    public static void main(String[] args) {
        char[] array = {'a','b','c'};
        String str = new String(array);
        System.out.println(str);//输出abc
    }
 
2.7 字符串转换
使用一个特定的新的字符串换掉已有的字符串数据,可用的方法如下:
 1)替换所有的指定内容:String replaceAll ( String regex, String replacement );
    public static void main(String[] args) {
        String str = "cdabstab";
        System.out.println(str.replaceAll("ab", "oo"));
    }
 
执行结果:
 
 上述代码是将字符串中所有的 " ab “,都替换为了” oo".
 2) 替换首个内容: String replaceFirst ( String regex, String replacement );
    public static void main(String[] args) {
        String str = "cdabstab";
        System.out.println(str.replaceFirst("ab", "oo"));
    }
 
上述代码是将字符串中的第一个 " ab “,替换为了” oo".
 注意这里是直接输出,所以能看到替换的效果,但如果最后输出的是str,那输出的还是原来的对象,因为字符串是不可变对象,替换不会修改当前的字符串,而是会产生一个新的字符串。
2.8 字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个子字符串。
 可用方法如下:
 1)按照字符串中的字符将字符串全部拆分:String[ ] split ( String regex )
    public static void main(String[] args) {
        String str = "hello world hello bit";
        String[] array = str.split(" ");
        System.out.println(Arrays.toString(array));
    }
 
执行结果:
 
 上述代码是将字符串str以空格拆分成了一个字符串数组。
 2) 字符串的部分拆分:
 将字符串以指定的格式,拆分为 limit 组:String[ ] split ( String regex, int limit )
    public static void main(String[] args) {
        String str = "hello world hello bit";
        String[] array = str.split(" ",2);
        System.out.println(Arrays.toString(array));
    }
 
执行结果:
 
 拆分是特别常用的操作,需要我们去重点掌握。另外有些特殊字符作为分割符可能无法正确切分,需要加上转义。
 例如,拆分IP地址:
    public static void main(String[] args) {
        String str = "192.168.1.1";
        String[] array = str.split("\\.");
        System.out.println(Arrays.toString(array));
    }
 
执行结果:
 
 注意事项:
- 字符 " | ", " * " " + " " . "都得加上转义字符,前面加上 " \ "
 - 如果是 " \ ",那么就得写成 " \\ "
 - 如果一个字符串中有多个分割符,可以用 " | "作为连字符
示例代码:多次拆分 
    public static void main(String[] args) {
        String str = "name=zhangsan&age=18";
        String[] array = str.split("=|&");
        System.out.println(Arrays.toString(array));
    }
 
执行结果:
 
2.9 字符串截取
从一个完整的字符串中截取出部分内容,可用方法如下:
 1)String substring ( int beginIndex )
 功能:从指定索引截取到结尾
    public static void main(String[] args) {
        String str1 = "abcdef";
        String str2 = str1.substring(2);
        System.out.println(str2);
    }
 
上述代码是从字符串 str1 下标为2的地方开始截取(也包括2),一直截取到字符串结尾,输出的结果是 " cdef ".
 2) String substring ( int beginIndex , int endIndex )
 功能:截取字符串的部分内容
    public static void main(String[] args) {
        String str1 = "abcdef";
        String str2 = str1.substring(2,5);
        System.out.println(str2);
    }
 
上述代码是从字符串 str1 2下标的位置开始截取(包括2),一直到下标为5的地方结束(不包括5),所以输出的结果是 " cde ".
 需要注意的一点是这里的区间是左闭右开区间 [ ),假设x处于这个区间,那x的取值范围就是左边 ≤ x < 右边,也就是包含左边,但不包含右边。
其他操作
1)String trim ()
 功能:去掉字符串中的左右空格,保留中间空格
    public static void main(String[] args) {
        String str1 = " hello world ";
        String str2 = str1.trim();
        System.out.println(str1);
        System.out.println(str2);
    }
 
我们可以分别输出 str1 和 str2 来观察,trim 会去掉字符串开头和结尾的空白字符(空格、换行、制表符等)。
3. 字符串的不可变性
String 是一种不可变对象,字符串中的内容是不可改变的,字符串不可修改,是因为:
- String类在设计时就是不可改变的,String类在描述中就已经说明了。

String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:
1.String类被final 修饰,表明该类不能被继承
2.value被final修饰,表明value自身的值不能改变,即不能引用其他字符数组,但是其引用空间中的内容可以修改。 - 所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象

上面提到的大小写转换也是一样:

final 修饰类表明该类不想被继承,final 修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。 
    public static void main(String[] args) {
        final int[] array = {1,2,3,4,5};
        array[0] = 100;
        array = new int[]{4,5,6};
    }
 
执行结果:
 
为什么 String 要设计成不可变的?(不可变对象的好处是什么?)
1.方便实现字符串对象池。如果 String 可变,那么对象池就需要考虑写时拷贝的问题了。
2.不可变对象是线程安全的。
3.不可变对象更方便缓存 hash code,作为 key 时可以更高效的保存到 HashMap 中。
那如果想要修改字符串中的内容,该如何操作呢?
 注意:尽量避免直接对 String 类对象进行修改,因为 String 类是不能修改的,所有的修改都会创建新对象,效率非常低下。
 可以看待在对String类进行修改时,效率是非常慢的,因此:尽量避免对String的直接需要,如果要修改建议尽量使用StringBuffer 或者 StringBuilder.
4.StringBuffer 和 StringBuilder
4.1 StringBuilder 的介绍
由于String的不可修改特性,为了方便字符串的修改,Java 中又提供了StringBuffer 和 StringBuilder类。这两个类大部分功能是相同的,这里介绍StringBuilder常用的一些方法。
| 方法 | 说明 | 
|---|---|
| StringBuffer append ( String str ) | 在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、double、float、int、long、Object、String、StringBuffer的变量 | 
| char charAt ( int index ) | 获取index位置的字符 | 
| int length( ) | 获取字符串的长度 | 
| int capacity( ) | 获取底层保存字符串空间总的大小 | 
| void ensureCapacity ( int minimumCapacity ) | 扩容 | 
| void setCharAt ( int index, char ch ) | 将index位置的字符设置成ch | 
| int indexOf (String str ) | 返回str第一次出现的位置 | 
| int indexOf (String str, int fromIndex ) | 从fromIndex位置开始查找str第一次出现的位置 | 
| int lastIndexOf ( String str ) | 返回最后一次出现str的位置 | 
| int lastIndexOf ( String str,int fromIndex ) | 从fromIndex位置开始查找str最后一次出现的位置 | 
| StringBuffer insert ( int offset, String str ) | 在offset位置插入:八种基本类型&String类型&Object类型 | 
| StringBuffer deleteCharAt (int index ) | 删除index位置的字符 | 
| StringBuffer delete ( int start, int end ) | 删除[start, end]区间内的字符 | 
| StringBuffer replace ( int start, int end, String str ) | 将[start, end]位置的字符替换为str | 
| String substring ( int start ) | 从start开始一直到末尾的字符以String的方式返回 | 
| String substring ( int start, int end ) | 将[start, end]范围内的字符以String的方式返回 | 
| StringBuffer reverse ( ) | 反转字符串 | 
| String toString ( ) | 将所有字符按照String的方式返回 | 
    public static void main(String[] args) {
        String str = "hello";
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(str);
        stringBuilder.append(" world");
        str = stringBuilder.toString();
        System.out.println(str);
    }
 
运行结果:
 
面试题
- String、StringBuffer、StringBuilder的区别
 - String的内容不可修改,StringBuilder和StringBuffer的内容可以修改
 - StringBuilder和StringBuffer的大部分功能是相似的
 - StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
 



















