String类的申明

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

String类用了final修饰符,表示它不可以被继承,同时还实现了三个接口, 实现Serializable接口表示String类可被序列化;实现Comparable<T> 接口主要是提供一个compareTo 方法用于比较String字符串;还实现了CharSequence 接口,这个接口代表的是char值得一个可读序列(CharBufferSegmentStringStringBufferStringBuilder也都实现了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,毕竟线程同步也会带来一定的消耗。

JDK源码分析-String、StringBuilder、StringBuffer的更多相关文章

  1. 深入源码剖析String,StringBuilder,StringBuffer

    [String,StringBuffer,StringBulider] 深入源码剖析String,StringBuilder,StringBuffer [作者:高瑞林] [博客地址]http://ww ...

  2. JDK源码分析(一)—— String

    dir 参考文档 JDK源码分析(1)之 String 相关

  3. 【JDK】JDK源码分析-LinkedHashMap

    概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ...

  4. 【JDK】JDK源码分析-Semaphore

    概述 Semaphore 是并发包中的一个工具类,可理解为信号量.通常可以作为限流器使用,即限制访问某个资源的线程个数,比如用于限制连接池的连接数. 打个通俗的比方,可以把 Semaphore 理解为 ...

  5. 【JDK】JDK源码分析-HashMap(2)

    前文「JDK源码分析-HashMap(1)」分析了 HashMap 的内部结构和主要方法的实现原理.但是,面试中通常还会问到很多其他的问题,本文简要分析下常见的一些问题. 这里再贴一下 HashMap ...

  6. JDK源码学习--String篇(二) 关于String采用final修饰的思考

    JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JD ...

  7. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  8. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

  9. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

随机推荐

  1. ffdshow 源代码分析 4: 位图覆盖滤镜(滤镜部分Filter)

    ===================================================== ffdshow源代码分析系列文章列表: ffdshow 源代码分析 1: 整体结构 ffds ...

  2. 【15】-java实现二分查找

    二分查找在面试中经常被遇到,这个方法十分优雅 介绍 二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它.一开始,范围覆盖整个数组 ...

  3. obj-c编程17:键值观察(KVO)

    说完了前面一篇KVC,不能不说说它的应用KVO(Key-Value Observing)喽.KVO类似于ruby里的hook功能,就是当一个对象属性发生变化时,观察者可以跟踪变化,进而观察或是修正这个 ...

  4. 基于event 实现的线程安全的优先队列(python实现)

    event 事件是个很不错的线程同步,以及线程通信的机制,在python的许多源代码中都基于event实现了很多的线程安全,支持并发,线程通信的库 对于优先队列的堆实现,请看<python下实现 ...

  5. (WPS) 网络地理信息处理服务

    WPS 标准为网络地理信息处理服务提供了标准化的输入和输出. OGC® Web Processing Service (WPS) 标准描述了如何通过远程的任何算法和模型处理获得地理空间的栅格或矢量信息 ...

  6. AngularJS学习笔记之directive——scope选项与绑定策略

    开门见山地说,scope:{}使指令与外界隔离开来,使其模板(template)处于non-inheriting(无继承)的状态,当然除非你在其中使用了transclude嵌入,这点之后的笔记会再详细 ...

  7. Python_PyMySQL数据库操作

    连接数据库: conn=pymysql.connect(host=,user=',charset='utf8') 建立游标: cur = conn.cursor() 创建一个名字叫 lj 的数据库: ...

  8. 一篇迟到的gulp文章

    前言 这篇文章本应该在去年17年写的,但因为种种原因没有写,其实主要是因为懒(捂脸).gulp出来的时间已经很早了,16年的时候还很流行,到17年就被webpack 碾压下去了,不过由于本人接触gul ...

  9. Ocelot中文文档-服务发现

    Ocelot允许您指定服务发现提供程序,并使用它来查找Ocelot正在将请求转发给下游服务的主机和端口.目前,这仅在GlobalConfiguration部分中受支持,这意味着所有ReRoute将使用 ...

  10. Java虚拟机-内存tips

    java虚拟机内存可以分为独占区和共享区. 独占区:虚拟内存栈.本地方法栈.程序计数器. 共享区:方法区.Java堆(用来存放对象实例). 程序计数器 比较小的内存空间,当前线程所执行的字节码的行号指 ...