常用类型_字符串..

1.字符串

在Java中用java.lang.String来表示字符串
字符串底层使用字符数组来储存字符串的每一个字符 但是从Java9开始 就改成使用byte[]去储存字符串中的字符内容(但是你不能够说字符串等价于字符数组 在Java中 这种说法不成立)
所有字符串字面量都是字符串实例
字符串对象一旦创建成功以后 他的内容是不可以被修改的

2.字符串常量池

在Java中 有一个字符串常量池(SCP 即String Constant Pool)
从Java7开始他属于堆空间的一部分(以前属于方法区的一部分)

当遇到字符串字面量时 会去查看SCP
如果SCP中存在与字符串字面量内容一样的对象A的话 那么就会返回对象A
否则 就会创建一个字符串对象C 加入SCP 然后返回对象C

3.字符串的初始化

接下来主要给定一个实例 去分析底层的内存细节
其中我还想解释一下第二行代码 首先遇到字符串字面量 就回去SCP中寻找 发现存在和字面量一致的对象 直接返回 所以其实"mj"所在的位置就变成了一个String对象 接着由于调用了带参且参数为String类型的构造方法 所以就会将指定字符数组拷贝给s2中的字符数组 然后字符串对象本身的内存会在SCP以外的堆空间开辟

public class Main {
    public static void main(String[] args) {
        String s1 = "mj";
        String s2 = new String("mj");
        String s3 = new String(s1);
        String s4 = new String(s2);
        char[] cs = {'m', 'j'};
        String s5 = new String(cs);
        String s6 = new String(s5);
    }
}

常用类型_字符串.._第1张图片
我们可以通过断点调试功能来作证一下我们的想法的正确性
常用类型_字符串.._第2张图片

可以看到每一个在main方法中创建的局部变量的id都是不一样 这可以反映出每一个对象的内存都是不一样的(但让内存地址也有这样的作用 但不能说id和内存地址是等价的)
然后也可以看到前四个字符串对象中的字符数组对象是一致的 然后s5和s6的字符数组对象是一致的并且他们的字符数组是有别于cs的字符数组对象的

然后在给定一个实例 通过这个实例我想说明的是 字符串拼接的底层操作是:
这个不用关注底层中关于SCP的细节 所以不用分析对象是否归属于SCP

public class Main {
    public static void main(String[] args) {
        String s = "555";
        s += "555";
        s = "666";
        test(s);
    }
    public static void test(String str){
        str += "666";
    }
}

从断点调试的结果来看 除了参数传递过程中的字符串指向一致以外 其他的指向各不相同

4.intern方法

A.intern的作用:
如果SCP中存在和A对象内容一致的对象C的话 那么就直接返回C
否则的话 就将将对象A加入到SCP中 然后返回对象A

我必须得说明的一点是:格式化字符串不是字符串字面量 所以说他压根不存在存不存储在SCP中的问题 换句话说 他不可能储存在SCP中 并且String.format返回值是一个新的String对象

public class Main {
    public static void main(String[] args) {
        String s1 = String.format("%d%d%d", 1, 2, 3);
        String s2 = String.format("%d%d%d", 1, 2, 3);
        String s3 = s1.intern();
        String s4 = s2.intern();
        String s5 = "123";
        System.out.println(s1 == s2);// false
        System.out.println(s1 == s3);// true
        System.out.println(s1 == s4);// true
        System.out.println(s1 == s5);// true
    }
}

其内存细节如图所示
常用类型_字符串.._第3张图片
通过断点调试以后的结果如下所示
常用类型_字符串.._第4张图片
从结果上来看 s1、s3、s4、s5指向的字符串对象以及其中的字符数组对象都是一样的 这也进一步佐证了我们作图的正确性

5.字符串的常见方法

public class Main {
    public static void main(String[] args) {
        // trim() 用于去除字符串中前面和后面的空格
        String s1 = " 123 456  ".trim();
        System.out.println(s1);// "123 456"
        // toUpperCase() 将小写字母转换成大写字母
        String s2 = "abc".toUpperCase();
        System.out.println(s2);// ABC
        // toLowerCase() 将大写字母转换成小写字母
        String s3 = "ABC".toLowerCase();
        System.out.println(s3);// abc
        // contains() 查看是否包含指定字符或字符串
        boolean b1 = "12345".contains("1");
        boolean b2 = "12345".contains("12");
        System.out.println(b1);// true
        System.out.println(b2);// true
        // startsWith() 查看是否以某个字符或者字符串开头
        boolean b3 = "12345".startsWith("1");
        boolean b4 = "12345".startsWith("12");
        System.out.println(b3);// true
        System.out.println(b4);// true
        // endsWith() 查看是否以某个字符或者字符串结尾
        boolean b5 = "12345".endsWith("5");
        boolean b6 = "12345".endsWith("45");
        System.out.println(b5);// true
        System.out.println(b6);// true
        // split() 将字符串按照指定的字符进行分割 并且返回一个字符串数组
        String[] strs = "1_2_3_4_5".split("_");
        for(String str: strs){
            System.out.print(str + " ");
        }
        System.out.println();
        // compareTo()和compareToIgnoreCase 用于逐位比较两个字符串
        System.out.println("abc".compareTo("abC"));// >0
        System.out.println("abc".compareToIgnoreCase("abC"));// =0
        // equals()和equalsIgnoreCase() 用于比较两字符串是否相等
        System.out.println("abc".equals("abC"));// false
        System.out.println("abc".equalsIgnoreCase("abC"));// true
    }
}

6.字符串的截取操作

涉及到字符串的两个方法indexOf/lastIndexOf和substring
我们需要讲清楚indexOf和lastIndexOf的区别在于:前者是从前往后找第一个指定字符 后者是从后往前找指定字符 但是两者的索引都是从前往后数的 这是毋庸置疑的
然后substring是左闭右开的

public class Main {
    public static void main(String[] args) {
        String str = "/user/mj.com";
        System.out.println(str.lastIndexOf("/"));// 5
        System.out.println(str.indexOf("."));// 8
        System.out.println(str.substring(str.lastIndexOf("/") + 1, str.indexOf(".")));// mj
        System.out.println(str.substring(str.indexOf(".") + 1));// com
    }
}

7.StringBuilder

在进行大量的字符串改动操作时 我们推荐使用StringBuilder类(比如字符串相关的代替、拼接操作)
使用StringBuilder类的好处在于:
我们可以节省空间、提高程序性能 但是如果我们使用String类的话 就会消耗空间 降低程序性能

public class Main {
    public static void main(String[] args) {
        String s1 = "";
        s1 += "123";
        s1 += "123";
        StringBuilder sb = new StringBuilder();
        sb.append("123").append("123");
    }
}

从上述代码我们可以知道:如果使用String完成字符串的拼接操作的话 创建字符串对象的时机为每一次的拼接的时候
但是如果使用StringBuilder完成字符串的拼接操作的话 创建数组对象的时机就是执行扩容操作的时候 可以见的 使用StringBuilder可以更加节省空间(而且每一次append的返回值都是this 所以我们可以进行append的连写操作)
那么程序性能方面如何体现呢

public class Main {
    public static void main(String[] args) {
        testString();// 4
        testStringBuilder();// 0
    }
    public static void testString(){
        long begin = System.currentTimeMillis();
        String s1 = "";
        s1 += "123";
        s1 += "123";
        long end = System.currentTimeMillis();
        System.out.println("String->" + (end - begin));
    }
    public static void testStringBuilder(){
        long begin = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        sb.append("123").append("123");
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder->" + (end - begin));
    }
}

从结果可以看出 使用StringBuilder用时比String还少 执行效率高的多了

对于StringBuilder来说 他的常用方法有:append、insert、delete、replace、reverse等

值得注意的是:
StringBuilder并不是String的子类或者父类 但是他们都继承自CharSequence 而且他们的有些方法参数类型为CharSequence 所以你可以使用他的子类String或者StringBuilder

1.StringBuilder的append原理

他的原理就是:
StringBuilder里面有一个字符数组用来储存字符串中的每一个字符 当调用append方法的时候 会往这个字符数组中添加字符串 并且他存在扩容操作
其中StringBuilder的默认容量是16 而且扩容操作是将原来容量变成2倍+2

public class Main {
    public static void main(String[] args) {
        // 假设StringBuilder中的字符数组的默认容量是6的话 
        StringBuilder sb = new StringBuilder();
        sb.append("123").append("456").append("789");
    }
}

其内存细节为:
常用类型_字符串.._第5张图片

你可能感兴趣的:(java)