浅析 String

浅析String

  • 一、创建字符串
  • 二、字符串与常量池
  • 三、字符串的不可变性
  • 四、字符串的拼接
  • 五、StringBuilder和StringBuffer

一、创建字符串

    //三种常用的构造字符串的方式
    public static void main(String[] args) {
        String str1 = "hello";
        
        String str2 = new String("world");
        
        char[] chars = {'a','b','c'};
        String str3 = new String(chars);
    }

注意: String是引用类型,内部并不存储字符串本身。通过查看String类的实现源码可以发现,字符串实际由两部分value数组、hash值组成,字符串实际保存在char类型的数组中:

浅析 String_第1张图片

二、字符串与常量池

以下程序输出的结果是什么?在此过程中创建了几个String对象?

    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = "Hello";

        String str3 = new String("Hello");
        System.out.println(str1==str2);
        System.out.println(str1==str3);
    }

分析: 双引号引起来的内容时存放在字符串常量池的,在直接赋值时,如果在字符串常量池中存在就直接返回常量池中字符串的引用,如果不存在则先在字符串常量池中创建一份。如果通过new创建字符串对象,同样会按照如上步骤检查常量池,只不过最后返回的是通过new创建的字符串对象。

最后我们说结论:此过程中str1会在常量池创建1个字符串对象,str2创建0个字符串对象,str3在堆区创建1字符串个对象。且str1==str2str1!=str3.

具体过程如下草图:
浅析 String_第2张图片
总结:

直接赋值产生10个字符串对象,使用newString()赋值时产生21字符串对象。赋值时先看字符串常量池,如果字符串常量池中没有,就在常量池中创建一个,如果有,前者直接赋值则直接引用,后者使用new String()在堆内存中还需创建一个实例对象(此时引用变量指向的是堆内存中创建的实例对象,而不是常量池中的实例对象)。

字符串常量池的作用:

“池” 是编程中的一种常见的, 重要的提升效率的方式。 对于字符串常量池来说,每次使用相同字面类型的常量时,Java会首先在字符串常量池中查找是否存在该常量的实例,如果存在则直接返回引用,避免重复创建新的实例,从而提高程序的运行速度并节省内存

三、字符串的不可变性

1.String类在设计时就是不可改变的,String类实现描述中已经说明了。

浅析 String_第3张图片

2.所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象。

浅析 String_第4张图片

为什么 String 要设计成不可变的?

  1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
  2. 不可变对象是线程安全的.
  3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中

四、字符串的拼接

上面我们说String被设计成不可变类型,那么字符串的拼接该怎么解释呢?

    public static void main(String[] args) {
        String str="hello";
        str+="world";
        System.out.println(str);
    }

我们将上面的代码进行编译,其实它的底层实现如下:

    public static void main4(String[] args) {
        //上述代码的底层实现:
        String str = "hello";

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(str);
        stringBuilder.append("abc");
        str = stringBuilder.toString();

        System.out.println(str);
    }

所以,每次字符串的拼接底层都会创建一个StringBuilder对象,最后通过toString再返回一个新的String对象,可以想象如果在一个循环中使用字符串的拼接,那么它的效率将会非常低。话说回来,上面提到了StringBuilder,他究竟是什么?下面我们详细介绍:

五、StringBuilder和StringBuffer

由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilderStringBuffer类。与String类的不同就是,在这些类中的对字符串修改的方法都是直接对原字符串进行修改,最后返回的都是修改后的原字符串。

此外,StringBuffer和StringBulider方法都是一样的,区别是StringBuffer被synchronized(锁)修饰,(线程安全)用在多线程情况下。单线程下一般用StringBuilder,因为频繁的加锁和释放锁也是需要耗费系统资源的。

(1)String、StringBuffer、StringBuilder的区别

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

(2)三者之间的转换

  1. StringBuffer或StringBuilder转String:调用toString方法
  2. String转StringBuffer或StringBuilder:利用它们的构造方法或append()方法

经典例题: 在不考虑常量池之前是否存在的情况下,以下总共创建了多少个String对象?

String str = new String("ab"); // 会创建多少个对象
String str = new String("a") + new String("b"); // 会创建多少个对象
//答案:2 5

解析:

对于代码 String str = new String(“ab”); 会创建两个String对象。首先,"ab"字面量会在字符串常量池中创建一个String对象,然后通过调用new String()构造函数创建第二个String对象。

对于代码 String str = new String(“a”) + new String(“b”); 会创建五个String对象。首先,字面量 “a” 和 “b” 分别会在字符串常量池中创建两个String对象。接着,通过 new String() 构造函数创建了另外两个String对象。最后,通过字符串拼接操作符 + 进行连接时,会创建一个新的String对象,其值为拼接结果 “ab”。

你可能感兴趣的:(java,开发语言,经验分享)