工作三年,小胖连 String 源码都没读过?真的菜!(下)

简介: 工作三年,小胖连 String 源码都没读过?真的菜!

String 为啥用 final 修饰?(String 为什么设计成不可变的呢?)


1、安全


  • 引发安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在 socket 编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞
  • 保证线程安全,在并发场景下,多个线程同时读写资源时,会引竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程
  • HashCode,当 String 被创建出来的时候,hashcode 也会随之被缓存,hashcode 的计算与 value 有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。


「2、高效」


第二个好处是高效,以 JVM 中的字符串常量池来举例,如下两个变量:


String s1 = "Java";
String s2 = "Java";


  • 只有字符串是不可变时,我们才能实现字符串常量池,字符串常量池可以为我们缓存字符串,提高程序的运行效率,如下图所示:


640.png


如果 String 是可变的,那当 s1 的值修改之后,s2 的值也跟着改变了,这样就和我们预期的结果不相符了,因此也就没有办法实现字符串常量池的功能了。


== 和 equals 方法的区别?


「== 对于基本数据类型来说,是用于比较 "值" 是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的」。比如:


int a = 1;
int b = 2;
System.out.println(a==b); // 输出 true
Integer a = 128;
Integer b = 128;
System.out.println(a==b); // 输出 false


再到 equals 方法,查看源码我们可以知道 Object 中也有 equals ()  方法,源码如下:


public boolean equals(Object obj) {
    return (this == obj);
}


不难看出,Object 的 equals 其实就是 "=="。而 String 中的 equals 重写了 Object 中的,把它修改成比较两个字符串的值是否相等。源码如下:


public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}


String、StringBuffer、StringBuilder 的区别?


640.png


先上一张三者的继承关系,首先 String 直接继承于 CharSequence,StringBuilder 和 StringBuffer 间接继承于 CharSequence,中间还多了个 AbstractStringBuilder。内部都是用一个 char 数组实现,虽然它们都与字符串相关,但是其处理机制不同:


  • String:是不可改变的量,也就是创建后就不能在修改了。


  • StringBuffer:是一个可变字符串序列,它与 String 一样,在内存中保存的都是一个有序的字符串序列(char 类型的数组),不同点是 StringBuffer 对象的值都是可变的。


  • StringBuilder:与 StringBuffer 类基本相同,都是可变字符换字符串序列,不同点是 StringBuffer 是线程安全的,StringBuilder 是线程不安全的。


「使用场景」


  • 使用 String 类的场景:在字符串不经常变化的场景中可以使用 String 类,例如常量的声明、少量的变量运算。


  • 使用 StringBuffer 类的场景:在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用 StringBuffer,例如 XML 解析、HTTP 参数解析和封装。


  • 使用 StringBuilder 类的场景:在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在单线程的环境中,则可以考虑使用 StringBuilder,如 SQL 语句的拼装、JSON 封装等。


JVM 对 String 做了那些优化?


String 常见的创建方式有两种,new String () 的方式和直接赋值的方式。


  • 「直接赋值会先去字符串常量池中查找是否已经有此值,如果有则把引用地址直接指向此值,否则会先在常量池中创建,然后再把引用指向此值」


  • 「new String () 的方式一定会先在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串」


比如下面代码:


String s1 = new String("Java");
String s2 = s1.intern();
String s3 = "Java";
System.out.println(s1 == s2); // 输出 false
System.out.println(s2 == s3); // 输出 true


640.png


除此以外,JVM 还会对 String 字符串做一些优化,例如以下代码:


String s1 = "Ja" + "va";
String s2 = "Java";
System.out.println(s1 == s2);


虽然 s1 拼接了多个字符串,但对比的结果却是 true,使用反编译工具,看到的结果如下:


Compiled from "StringExample.java"
public class com.lagou.interview.StringExample {
  public com.lagou.interview.StringExample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0
  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String Java
       2: astore_1
       3: ldc           #2                  // String Java
       5: astore_2
       6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: aload_1
      10: aload_2
      11: if_acmpne     18
      14: iconst_1
      15: goto          19
      18: iconst_0
      19: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      22: return
    LineNumberTable:
      line 5: 0
      line 6: 3
      line 7: 6
      line 8: 22
}


从编译代码 #2 可以看出,代码 "Ja"+"va" 被直接编译成了 "Java" ,所以 s1==s2 的结果才是 true,这就是编译器对字符串优化的结果。

相关文章
|
6月前
|
存储 安全 编译器
Go语言源码剖析-String和unsafe包
Go语言源码剖析-String和unsafe包
44 0
|
9月前
|
存储 安全 Java
高频面试题-JDK集合源码篇(String,ArrayList)
都是List的子集合,LinkedList继承与Dqueue双端队列,看名字就能看出来前者是基于数组实现,底层采用Object[]存储元素,数组中的元素要求内存分配连续,可以使用索引进行访问,它的优势是随机访问快,但是由于要保证内存的连续性,如果删除了元素,或者从中间位置增加了元素,会设计到元素移位的操作,所以增删比较慢。
56 0
|
9月前
|
存储 消息中间件 缓存
从源码上聊聊Redis-String、List的结构实现
本文的数据类型只讲底层结构和部分机制,不讲具体的使用,使用的话自行bing,但是会提一些应用场景
139 1
从源码上聊聊Redis-String、List的结构实现
|
9月前
|
存储 安全 Java
【JavaSE】Java基础语法(三十七):Java 中的 String 类(源码级别)(2)
2.11 char[] toCharArray() 2.12 String substring(int beginIndex) 从传入的索引处截取,截取到末尾,得到新的字符串 2.13 String substring(int beginIndex, int endIndex) 根据开始和结束索引进行截取,得到新的字 符串(包含头,不包含尾)
|
9月前
|
存储 Java
【JavaSE】Java基础语法(三十七):Java 中的 String 类(源码级别)(1)
String 表示 字符串类型,属于 引用数据类型 。Java 中 String 是 不可变 的。 在 Java 当中 双引号 括起来的字符串,是直接存储在“方法区”的“字符串常量池”当中的。 1. 构造方法 1.1 String()
|
10月前
|
NoSQL 编译器 Serverless
Redis源码中字符串String的实现
Redis源码中字符串String的实现
36 0
|
11月前
|
存储 缓存 安全
Java - String源码解析及常见面试问题
Java - String源码解析及常见面试问题
73 0
|
存储 NoSQL Java
Redis源码剖析之SDS(Simple Dynamic String)
Redis源码剖析之SDS(Simple Dynamic String)
95 0
Redis源码剖析之SDS(Simple Dynamic String)
JDK源码系列(3)-String
JDK源码系列(3)-String
JDK源码系列(3)-String
|
存储 缓存 安全
细读源码之Java String (一)
细读源码之Java String (一)
68 0
细读源码之Java String (一)