JDK源码分析之String、StringBuilder和StringBuffer

前言

本文主要介绍了关于JDK源码分析之String、StringBuilder和StringBuffer的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

String类的申明

public final class String
 implements java.io.Serializable, Comparable<String>, CharSequence {…}

String类用了final修饰符,表示它不可以被继承,同时还实现了三个接口, 实现Serializable接口表示String类可被序列化;实现Comparable<T> 接口主要是提供一个compareTo 方法用于比较String字符串;还实现了CharSequence 接口,这个接口代表的是char值得一个可读序列(CharBuffer, Segment, String, StringBuffer, StringBuilder也都实现了CharSequence接口)

String主要字段、属性说明

/*字符数组value,存储String中实际字符 */
private final char value[];
/*字符串的哈希值 默认值0*/
private int hash;
/*字符串的哈希值 默认值0*/
/*一个比较器,用来排序String对象, compareToIgnoreCase方法中有使用 */
public static final Comparator<String> CASE_INSENSITIVE_ORDER
      = new CaseInsensitiveComparator();

String 部分方法分析

String类提供了系列的构造函数,其中有几个都已经不推荐使用了,如下图:

构造函数

以下是两个常用的构造函数的实现:

//String str = new String(“123”)
public String(String original) {
  this.value = original.value;
  this.hash = original.hash;
}

//String str3 = new String(new char[] {'1','2','3'});
public String(char value[]) {
   //将字符数组值copy至value
  this.value = Arrays.copyOf(value, value.length);
 }

boolean equals(Object anObject)

String 类重写了 equals 方法,将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。

public boolean equals(Object anObject) {
  //直接将对象引用相比较,相同返回true
  if (this == anObject) {
   return true;
  }
  //比较当前对象与anObject的字符序列value
  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;
 }

int compareTo(String anotherString)

逐位比较两个字符串的字符序列,如果某一位字符不相同,则返回该位的两个字符的Unicode 值的差,所有位都相同,则计算两个字符串长度之差,两个字符串相同则返回0

public int compareTo(String anotherString) {
  int len1 = value.length;
  int len2 = anotherString.value.length;
  //取长度较小的字符串的长度
  int lim = Math.min(len1, len2);
  char v1[] = value;
  char v2[] = anotherString.value;

  int k = 0;
  while (k < lim) {
   //将两个字符串的字符序列value逐个比较,如果不等,则返回该位置两个字符的Unicode 之差
   char c1 = v1[k];
   char c2 = v2[k];
   if (c1 != c2) {
    return c1 - c2; //返回Unicode 之差

   }
   k++;
  }
  //长度较小的字符串所有位都比较完,则返回两个字符串长度之差
  //如果两个字符串相同,那么长度之差为0,即相同字符串返回0
  return len1 - len2;
 }

compareToIgnoreCase(String str)方法实现于此类似,比较时忽略字符的大小写,实现方式如下:

public int compare(String s1, String s2) {
   int n1 = s1.length();
   int n2 = s2.length();
   int min = Math.min(n1, n2);
   for (int i = 0; i < min; i++) {
    char c1 = s1.charAt(i);
    char c2 = s2.charAt(i);
    if (c1 != c2) {
     c1 = Character.toUpperCase(c1);
     c2 = Character.toUpperCase(c2);
     if (c1 != c2) {
      c1 = Character.toLowerCase(c1);
      c2 = Character.toLowerCase(c2);
      if (c1 != c2) {
       // No overflow because of numeric promotion
       return c1 - c2;
      }
     }
    }
   }
   return n1 - n2;
  }

native String intern()

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。

所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作,例如:String str1 = "123";

String内存位置:常量池OR堆

String对象可以直接通过字面量创建,也可以通过构造函数创建,有什么区别呢?

1.通过字面量或者字面量字符串通过”+”拼接的方式创建的String对象存储在常量池中,实际创建时如果常量池中存在,则直接返回引用,如果不存在则创建该字符串对象

2.使用构造函数创建字符串对象,则直接在堆中创建一个String对象

3.调用intern方法,返回则会将该对象放入常量池(不存在则放入常量池,存在则返回引用)

下面举例说明String对象内存分配情况:

String str1 = new String("123");
  String str2 = "123";
  String str3 = "123";
  String str4 = str1.intern();
  System.out.println(str1==str2); // false str1在堆中创建对象,str2在常量池中创建对象
  System.out.println(str2==str3); // true str2在常量池中创建对象,str3直接返回的str2创建的对象的引用 所以str2和str3指向常量池中同一个对象
  System.out.println(str4==str3); // true str4返回常量池中值为"123"的对象,因此str4和str2、str3都相等

关于字符串拼接示例:

public class StringTest {
 public static final String X = "ABC"; // 常量X
 @Test
 public void Test() {

  String str5 = new String("ABC");
  String str6 = str5+"DEF"; //堆中创建
  String str7 = "ABC"+"DEF"; //常量池
  String str8 = X+"DEF";  //X为常量,值是固定的,因此X+"DEF"值已经定下来为ABCDEF,实际上编译后得代码相当于String str8 = "ABCDEF"
  String str9 = "ABC";
  String str10 = str9+"DEF"; //堆中

  System.out.println(str6==str7); //false
  System.out.println(str8==str7); //true
  System.out.println(str10==str7); //false

  System.out.println(X==str9); //true
} }

反编译后的代码看一下便一目了然:

内存分配如下:

String、StringBuffer、StringBuilder

由于String类型内部维护的用于存储字符串的属性value[]字符数组是用final来修饰的:

/** The value is used for character storage. */
private final char value[];

表明在赋值后可以再修改,因此我们认为String对象一经创建后不可变,在开发过程中如果碰到频繁的拼接字符串操作,如果使用String提供的contact或者直接使用”+”拼接字符串会频繁的生成新的字符串,这样使用显得低效。Java提供了另外两个类:StringBuffer和StringBuilder,用于解决这个问题:

看一下下面的代码:

String str1="123";
   String str2="456";
   String str3="789";
   String str4 = "123" + "456" + "789"; //常量相加,编译器自动识别 String str4=“123456789”
   String str5 = str1 + str2 + str3; //字符串变量拼接,推荐使用StringBuilder
   StringBuilder sb = new StringBuilder();
   sb.append(str1);
   sb.append(str2);
   sb.append(str3);

下面是StringBuilder类的实现,只截取了分析的部分代码:

public final class StringBuilder
 extends AbstractStringBuilder
 implements java.io.Serializable, CharSequence
{

 //拼接字符串
 @Override
 public StringBuilder append(String str) {
 //调用父类AbstractStringBuilder.append super.append(str); return this; } }
abstract class AbstractStringBuilder implements Appendable, CharSequence {
 /**
  * 存储字符串的字符数组,非final类型,区别于String类
  */
 char[] value;

 /**
  * The count is the number of characters used.
  */
 int count;

 public AbstractStringBuilder append(String str) {
  if (str == null)
   return appendNull();
  int len = str.length();
   //检查是否需要扩容
  ensureCapacityInternal(count + len);
  //字符串str拷贝至value
  str.getChars(0, len, value, count);
  count += len;
  return this;
}

 private void ensureCapacityInternal(int minimumCapacity) {
  // overflow-conscious code
  // minimumCapacity=count+str.length
  //拼接上str后的容量 如果 大于value容量,则扩容
  if (minimumCapacity - value.length > 0) {

    //扩容,并将当前value值拷贝至扩容后的字符数组,返回新数组引用
   value = Arrays.copyOf(value,
     newCapacity(minimumCapacity));
  }
 }

 //StringBuilder扩容
 private int newCapacity(int minCapacity) {
  // overflow-conscious code
  // 计算扩容容量
  // 默认扩容后的数组长度是按原数(value[])组长度的2倍再加上2的规则来扩展,为什么加2?
  int newCapacity = (value.length << 1) + 2;
  if (newCapacity - minCapacity < 0) {
   newCapacity = minCapacity;
  }
  return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
   ? hugeCapacity(minCapacity)
   : newCapacity;
 }
}

StringBuffer和StringBuilder用一样,内部维护的value[]字符数组都是可变的,区别只是StringBuffer是线程安全的,它对所有方法都做了同步,StringBuilder是线程非安全的,因此在多线程操作共享字符串变量的情况下字符串拼接处理首选用StringBuffer, 否则可以使用StringBuilder,毕竟线程同步也会带来一定的消耗。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • jdk中String类设计成final的原由

    最佳答案: 主要是为了 " 效率 " 和 " 安全性 " 的缘故. 若 String 允许被继承, 由于它的高度被使用率, 可能会降低程序的性能,所以 String 被定义成 final. 其它答案一: String 和其他基本类型不同 , 他是个对象类型. 既然是对象类型 , 如果是在静态方法下是必须调用静态方法或值的 , 如果是非静态的方法 , 就必须要实例化. main 函数是个 static 的. 所以 String 要能像其他的基本类型一样直接被调用. 这

  • 走进JDK之不可变类String

    文中相关源码: String.java 今天来说说 String. 贯穿全文,你需要始终记住这句话,String 是不可变类 .其实前面说过的所有基本数据类型包装类都是不可变类,但是在 String 的源码中,不可变类 的概念体现的更加淋漓尽致.所以,在阅读 String 源码的同时,抽丝剥茧,你会对不可变类有更深的理解. 什么是不可变类 ? 首先来看一下什么是不可变类?Effective Java 第三版 第 17 条 使不可变性最小化 中对 不可变类 的解释: 不可变类是指其实例不能被修改的

  • JDK源码分析之String、StringBuilder和StringBuffer

    前言 本文主要介绍了关于JDK源码分析之String.StringBuilder和StringBuffer的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 String类的申明 public final class String implements java.io.Serializable, Comparable<String>, CharSequence {-} String类用了final修饰符,表示它不可以被继承,同时还实现了三个接口, 实现Serializa

  • 通过JDK源码分析关闭钩子详解

    关闭钩子 用户关闭关闭程序,需要做一些善后的清理工作,但问题是,某些用户不会按照推荐的方法关闭应用程序,肯能导致善后工作无法进行.像tomcat调用server的start方法启动容器,然后会逐级调用start.当发出关闭命令是会启动关闭功能,但是关闭可能会有一些意外产生,导致应用程序没有进入到我们制定的关闭方法去.如何解决这个问题呢,使得即使有意外也能正常进入关闭流程. 好在java提供了一种优雅的方式去解决这种问题.使得关闭的善后处理的代码能执行.java的关闭钩子能确保总是执行,无论用户如

  • 通过JDK源码角度分析Long类详解

    概况 Java的Long类主要的作用就是对基本类型long进行封装,提供了一些处理long类型的方法,比如long到String类型的转换方法或String类型到long类型的转换方法,当然也包含与其他类型之间的转换方法.除此之外还有一些位相关的操作. Java long数据类型 long数据类型是64位有符号的Java原始数据类型.当对整数的计算结果可能超出int数据类型的范围时使用. long数据类型范围是-9,223,372,036,854,775,808至9,223,372,036,85

  • jdk源码阅读Collection详解

    见过一句夸张的话,叫做"没有阅读过jdk源码的人不算学过java".从今天起开始精读源码.而适合精读的源码无非就是java.io,.util和.lang包下的类. 面试题中对于集合的考察还是比较多的,所以我就先从集合的源码开始看起. (一)首先是Collection接口. Collection是所有collection类的根接口;Collection继承了Iterable,即所有的Collection中的类都能使用foreach方法. /** * Collection是所有collec

  • String StringBuilder StringBuffer区别以及源码分析

    目录 1. String/StringBuilder/StringBuffer 区别 1.1 String 1.2 StringBuilder 1.3 StringBuffer 2. String/StringBuilder/StringBuffer 源码 2.1 String 源码分析 2.1.1 String 类 2.1.2 String 类的属性 2.1.3 String 类的构造函数 2.1.4 String 类的常用方法 2.2 StringBuilder 源码分析 2.2.1 Str

  • Java从JDK源码角度对Object进行实例分析

    Object是所有类的父类,也就是说java中所有的类都是直接或者间接继承自Object类.比如你随便创建一个classA,虽然没有明说,但默认是extendsObject的. 后面的三个点"..."表示可以接受若干不确定数量的参数.老的写法是Objectargs[]这样,但新版本的java中推荐使用...来表示.例如 publicvoidgetSomething(String...strings)(){} object是java中所有类的父类,也就是说所有的类,不管是自己创建的类还是

  • 分析HashMap 的 JDK 源码

    缘由:今天好友拿着下面的代码,问我为什么 Map.Entry 这个接口没有实现 getKey() 和 getValue() 方法,却可以使用,由此,开启了一番查阅 JDK 源码的旅途-. Map map = new HashMap(); map.put(1, "张三"); map.put(2, "李四"); map.put(3, "王五"); map.put(4, "赵六"); map.put(5, "钱七"

  • JDK动态代理步骤详解(源码分析)

    动态代理步骤 1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法 2.创建被代理的类以及接口 3.通过Proxy的静态方法 通过Proxy的静态方法 ProxyObject proxyObject = new ProxyObject(); InvocationHandler invocationHandler = new DynamicProxy(proxyObject); ClassLoader classLoader = proxyObject.getCl

  • Java String源码分析并介绍Sting 为什么不可变

    Java String源码分析 什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变. 区分对象和对象的引用 对于Java初学者, 对于String是不可变对象总是存有疑惑.看下面代码: String s =

  • PipedWriter和PipedReader源码分析_动力节点Java学院整理

    PipedWriter和PipedReader源码分析 1. PipedWriter 源码(基于jdk1.7.40)  package java.io; public class PipedWriter extends Writer { // 与PipedWriter通信的PipedReader对象 private PipedReader sink; // PipedWriter的关闭标记 private boolean closed = false; // 构造函数,指定配对的PipedRea

随机推荐