String底层解析

2,336 阅读6分钟

目录

关于String

String类型的底层代码是Java所有底层代码中相对来说比较简单、好理解。同时,String底层的源码又是面试官经典的“面试第一题”。“良好的开端就是成功的一半”,因此这个问题回答的好坏会很大程度影响你接下来的面试。我会在这篇博客中梳理几个面试中频率较高的知识点。

String内部结构

String内部存储结构为char数组,有两个私有变量,一个是char[],哈希值。 具体结构:

 class String implements Serializable,Comparable<String>,CharSequence {
         //用于存储字符串的值
       private char value[];
          //缓字符串的hash code
       private int hash;
}

String的构造方法

String的构造方法有四个,前两个比较常见,参数是String字符串和char[]数组。 后两个是容易被忽略的小透明,参数是StringBuffer和StringBuilder

1.String为参数的构造方法

//String为参数的构造方法
      public String(String original){
          this.value = original.value;
          this.hash = original.hash;
     }

2.char[] 为参数的构造方法

//char[]为参数的构造方法
      public String(char value[]){
         this.value = Arrays.copyOf(value,value.length);
      }

3.StringBuffer为参数的构造方法

//StringBuffer为参数的构造方法
      public String(StringBuffer buffer){
          synchronized(buffer){
              this.value=Array.copyOf(buffer.getValue(),buffer.length())
          }
      }

4.StringBuilder为参数的构造方法

//StirngBuilder为参数的构造方法
      public String(StringBuilder builder){
           this.value = Arrays.copyOf(builder.getValue(),builder.length());
      }

String中的对比——equals()和compareTo()的对比

equals()方法

String中的equals()方法是重写了Object类中的equals()方法,其本质是利用了“==”。

equals()方法先检验对比双方的引用地址是否相等(“==”),如果地址相等,对比双方自然相等,返回true。然后检验需要对比的对象是否为String类型(“instanceof”),如果不是则返回false。之后对比两个字符串的长度是否对等(“==”),最后将两个字符串都转化为char[]形式,循环对比每一个字符。

源码:

public boolean equals(Object anObject){
           //对象引用相同直接返回true
           if(this==anObject){
               return true;
           }
          //判断需要对比的值是否为String类型,如果不是则返回false
           if(anObject instanceof String){
               String anotherString = (String)anObject;
               int n = value.length;
               if(n==anotherString.value.length){
                   //把两个字符串都转化为char数组对比
                   char v1[]=value;
                   char v2[]=anotherString.value;
                   int i=0;
                   //循环比对两个字符串的每一个字符
                   while(n--!=0){
                       //如果其中有一个字符不相等就true false,否则继续对比
                       if(v1[i]!=v2[i])
                           return false;
                       i++;
                   }
                   return true;
               }
           }
          return false;
      }

compareTo()方法

compareTo()方法不涉及地址的对比,它只是循环比较所有的字符串。当两个字符串中有任意一个字符不相同的时候,返回两个字符的差值。如果长度不对等则返回两个字符串长度的差值。

源码:

public int compareTo(String anotherString){
           int len1 = value.length;
           int len2 = anotherString.value.length;
           //获取到两个字符串长度最短的那个int值
           int lim = Math.min(len1,len2);
           char v1[]=value;
           char v2[]=anotherString.value;
           int k=0;
           //对比每一个字符
           while(k<lim){
               char c1=v1[k];
               char c2=v2[k];
              if(c1!=c2){
                   //有字符不相等就返回差值
                   return c1-c2;
               }
               k++;
           }
           //检验长度
           return len1-len2;
      }

小结:String中compareTo()和equals()方法的异同点

不同点:

  • equals()可以接收一个Object类型的参数,而compareTo()只能接收String类型的参数
  • equals()返回值为Boolean,而compareTo()的返回值则为int

相同点:

  • 两者都可以用于两个字符串的比较。当equals()方法返回true时,或是compareTo()方法返回0时,则表示两个字符串完全相同

String的常用方法清单

  • indexOf():查询字符串首次出现的下标位置
  • lastIndexOf():查询字符串最后出现的下标位置
  • contains(): 查询字符串中是否包含另一个字符串
  • toLowerCase(): 把字符串全部转换成小写
  • toUpperCase(): 把字符串全部转换成大写
  • length(): 查询字符串的长度
  • trim(): 去掉字符串首尾空格
  • replace():替换字符串中的某些字符
  • split(): 把字符串分割并返回字符串数组
  • join(): 把字符串数组转为字符串

关于equals()方法:“==”和equals()的区别?

“==”:

  • 对于基本数据类型来说,是比较两者的是否相等
  • 对于引用数据类型来说,是用于比较两者引用地址是否相等

equals():

  • String类型的equals()是重写了Object类中的equals()方法,他的基本实现是靠的“==”

Object类中的equal方法:

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

为什么用final修饰String类?

对于这个问题,Java之父James Gosling在一次记者采访中说明过,大体的原因如下:

1.安全 迫使String类被设计成不可变类的一个原因就是安全。(在进行系统的校验工作中,如果设为可变类,就有可能会出现严重的系统崩溃问题。)

举例:字符串常量池

2.高效 高司令是这样回答的:他会更倾向于使用不可变类(final),因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值。如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样性能上就会有一定的损失。

String和StringBuilder、StringBuffer的区别

首先,考虑到安全和性能问题,String类是不可变的,所以在字符串拼接的时候如果使用String的话性能会很低。因此就需要使用另一个数据类型StringBuffer。

StringBuffer:

  • 它提供了append和insert方法可用于字符串的拼接
  • 它使用了synchronized来保证线程安全

StringBuffer中append()方法:

			   public synchronized StringBuffer append(Object obj){
                   toStringCache = null;
                   super.append(String.valueOf(obj));
                   return this;
               }
               public synchronized StringBuffer append(String str){
                  toStringCache = null;
                   super.append(String.valueOf(str));
                   return this;
               }

但是由于StringBuffer保证了线程的安全,所以性能上来讲 —— 很低。 于是在JDK1.5中推出了线程不安全,但是性能相对而言较高的StringBuilder。其功能和StringBuffer一样。

String两种创建方法的异同

我们先来看看创建String的两种方式:

方式一:

String s1 = "java";  //直接赋值

方式二:

String s2 = new String("java");  //对象创建

这两种方法都可以创建字符串,但是两个方法在JVM的存储区域截然不同

方法一: jvm会先到字符串常量池中寻找是否有相同的字符串,如果有就返回常量句柄;如果没有该字符串,就先在常量池中创建此字符串,然后再返回常量句柄。

方法二: 直接在堆中创建此字符串,只有调用intern()才会放入字符串常量池中。

举例:

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

(s2,s3指向堆中常量池的“java”,s1指向堆中的“java”对象)

== 补充:jdk1.7之后把永生代换成了元空间,把字符串常量池从方法区移到了java堆上 ==

编译器对String字符串的优化

我们经常会用“+”来拼接两个字符串。即使是这样拼接的字符串,在进行比较时,也不会出现和结果相同字符串不对等的情况。这是编译器对于String的优化。

举例:

			String s1 = "ja" + "va";
            String s2 = "java";
            System.out.println(s1==s2); //true