浅析Java字符串String和intern()方法,StringBuilder和StringBuffer区别。

String虽然不属于Java的八大基本类型,但是在我们的日常开发中,使用它的次数是很频繁的。但是我想问下大家你们真的了解String么?
下面我从几个方面带大家重新了解下我们的String:

1.不可变性

我相信每个java开发者,都知道在我们java中的String它是不可变的。
但是它的不可变性具体体现在哪呢?
1.不可继承。
2.对象不可变
在我们的IDE中查看我们的String类,我们的String类,是final修饰的。我们都知道当final修饰一个类的时候,代表这个类是不能被继承的。
我们写个MyString类继承String。
在这里插入图片描述
可以看到编译器报错。
我们常说的String不可变,是对象的数据不可改变,而非引用不可变。String对象一旦创建,对象的值是不能修改的。

        String a = "abc";
        a = "ggg";
        System.out.println(a);

我们这段代码的打印结果是"ggg",而非"abc"。a = “ggg”;这句代码我们只是改变a的引用,而非改变了“abc”这个对象的值。我们的“abc"还在我们的常量池里。

2.String常量池

String在我们的Java使用过于频繁,为了避免String对象的重复创建,Java引入了常量池的概念。常量池是怎么避免String对象的重复创建的呢?
下面我们来看几行代码。

        String a = "abc";
        String b = "abc";
        System.out.println(a == b);

运行程序,控制台打印输出为true,告诉我们这两个对象是一个对象。为什么会这样呢,这就是我们常量池的作用了。当我们执行第一句代码的时候,先去我们的常量池里找,看是否存在“abc”,不存在创建一个“abc”放入常量池,并且返回它的引用。当我们执行第二段代码时,发现常量池里面已经存在“abc”了,直接返回它的引用。上述两行代码只创建了一个对象。
我再看一段代码:

        String a = "abc";
        String b = new String("abc");
        System.out.println(a == b);

这次控制台输出了false,为什么呢?因为我们的a引用的是我们常量池中的“abc”,而我们的b引用的是我们的java堆的上对象,new 关键字新建一个对象的时候,每次都是重新开辟一块内存空间。两者当然不是同一个对象。

经典问题:String a = new String(“abc”);?
分析这个问题,首先我们要明确执行这段代码,jvm做了哪些事。
首先jvm会去常量池中找,看是否存在“abc”,不存在就创建一个“abc”放入常量池。由于又存在new这个关键字,还会在java堆上创建一个对象,存储“abc”。
所以这个问题的答案是:如果常量池中存在“abc”上述代码就创建了一个对象,如果常量池中不存在,上述代码会创建两个对象,一个在常量,一个在java堆。

3.intern()方法的作用

我们的intern()方法可以把一个字符串放入常量池。
作用:首先去常量池中查找,是否存在当前字符串,如果存在直接返回它的引用,如果不存在创建这个字符串放入常量池,并返回它的引用。我们要明确一点intern()方法返回的一定是常量池中的引用。
下面我们来看一段代码。

        String a = "abc";
        String b = new String("abc").intern();
        System.out.println(a == b);

控制台输入为true。就如我们上面所说的,intern方法返回的是常量池中“abc”的引用,同a,所以我们的a和b又相等了。

4.字符串的拼接

一般字符串的拼接我们可以使用三种方法:
1:操作符"+"
2:StringBuilder
3:StringBuffer
我们先来看一个问题:
String a = “a”+“b”+“c”;创建了几个对象。可能有些人会说3个,有些人会说四个。其实这段代码只创建了一个对象。由于"a",“b”,"c"都是常量 ,对于常量,编译时就直接存储它们的字面值而不是它们的引用 。在jdk1.5之前会转化为,StringBuffer对象连续的append()操作。在jdk1.5之后会转化为StringBuilder对象连续的append操作。

        String a = "abc";
        String b = "a"+"b"+"c";

上述a,b是指向的同一个对象。
还有一点我要说的就是:避免在我们的for,while循环中使用“+”拼接字符串。
我们来看两端代码:
第一段:

        String result = "";
        for(int i = 0 ;i < 100 ;i++){
            result += i;
        }
        System.out.println(result);

第二段:

        StringBuilder sb = new StringBuilder();
        for(int i = 0 ;i < 100 ;i++){
           sb.append(i).append("");
        }
        System.out.println(sb.toString());

我们的上述两端代码打印的结果是一样的,实际开发中我推荐大家使用第二种。为什么?上面我们提到,通过“+”拼接字符串,底层都是通过StringBuilder的append()方法来实现的,如果我们在for循环中使用"+"拼接字符串,每一次循环,我们都会创建一个StringBuilder对象。如果循环1000次,10000次,显然会降低我们程序的性能。而第二段代码中,我们始终用的是一个StringBuilder来进行的字符串拼接操作。

5.StringBuilder和StringBuffer的区别:

查看我们的源代码

    //StringBuilder的构造方法,默认初始化了一个容量大小为16的char类型的数组。
    public StringBuilder() {
        super(16);
    }
    
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    
    //同StringBuilder
    public StringBuffer() {
        super(16);
    }
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
    @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

可以看到我们的StringBuilder和StringBuffer内部都是通过char[]来实现的。(jdk1.9后,底层把char 数组变成了byte[]。)唯一不同的就是我们的StringBuffer内部操作方法都加上了synchronized关键字,因为保证了线程安全,同时效率相比StringBuilder较低。

6.String不可变的好处

为什么Java要把String设计成不可变的呢,下面我们来讲一讲String不可变的好处。
1.安全,由于我们的String是不可变的,天生就具备了线程安全。
2.String经常作为参数,String不可变,代表我们的参数不可变。
3.常量池的需要,我们的String在我们的开发中,经常被使用,常量池会对我们的String进行缓存,只有String不可变,常量池再有意义。节约我们的内存空间。
4.当String作为我们的HashMap或者其他散列表key的时候,因为String不可变,所以其hash值也不会发生改变,我们可以不需要每次去计算,可以缓存其hash值,可以提高Hash表的效率。

你可能感兴趣的:(Java)