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. 【数值分析】误差的分析与减少及Matlab解线性方程的四种方法

    1.误差的来源 模型误差:数学模型与实际问题之间的误差 观测误差:测量数据与实际数据的误差 方法误差:数学模型的精确解与数值方法得到的数值解之间的误差:例如 舍入误差:对数据进行四舍五入后产生的误差 ...

  2. ubuntu下eclipse新建项目没有java project的解决办法

    装好了eclipse之后却发现新建项目没有java project的选项,大致搜索了一下,并没有发现很好的解决方案(大都是让你重新安装什么的),于是开始瞎鼓捣,并且找到了一个方案: 在终端切换到roo ...

  3. ROS探索总结(十四)——move_base(路径规划)

    在上一篇的博客中,我们一起学习了ROS定位于导航的总体框架,这一篇我们主要研究其中最重要的move_base包. 在总体框架图中可以看到,move_base提供了ROS导航的配置.运行.交互接口,它主 ...

  4. sqlserver2008中删除了windows用户,导致无法登陆的解决方案

    打开管理工具中的"服务",找到并关闭SQL Server服务.进入命令进入SQL Server 2008的安装目录,找到sqlservr.exe文件,执行命令:sqlservr - ...

  5. EBS R12安装升级(FRESH)(五)

    7.4.5 用DBUA升级 Database Upgrade Assistant提供图形界面进行升级. 将zysong.ttf复制到 /u01/oracle/TEST/db/tech_st/11.2. ...

  6. 面试之路(8)-BAT面试题之数组和链表的区别

    两种数据结构都是线性表,在排序和查找等算法中都有广泛的应用 各自的特点: 数组: 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素.但是如果要在数组中增加一个 ...

  7. linux下64位汇编的系统调用(2)

    知道了syscall调用号之后还不算完,还要搞清楚2件事: 1 每种调用号需要传递哪些参数: 2 调用如何传递参数以及结果如何返回: 第一个问题的答案是: 在linux系统中某个程序执行时进行的系统调 ...

  8. error C4996: 'strcpy': This function or variable may be unsafe.

    vs2012用strcpy遇到的错误. 错误描述:error C4996: 'strcpy': This function or variable may be unsafe. Consider us ...

  9. The 6th tip of DB Query Analyzer

      The 6th tip of DB Query Analyzer MA Gen feng (Guangdong Unitoll Services incorporated, Guangzhou ...

  10. miniUI中弹出框问题

    ---恢复内容开始--- 设置页面弹出框并提交弹出框内容 弹出按钮 <a class="btn_color_1" onclick="onEdit(0)"& ...