Java中,除了8种基本类型,最长用的应该就是String类了。那么我们来看看JDK中的源码是怎么建造String、StringBuffer、StrinBuilder一系列类的。


java.lang.String

在JAVA里,String类是一个非常特殊的类,我们来看一下它是怎么来表示一个字符串的。首先来看一下它的比较重要的几个属性,源码如下:

/** The value is used for character storage. */
private final char value[]; /** The offset is the first index of the storage that is used. */
private final int offset; /** The count is the number of characters in the String. */
private final int count;

是的,其实String是用一个char数组来储存的,offset表示char[]的起始位置。count表示字符串的长度,通过字符串的length()方法只有一行return count;就看得出来。

  这里的属性都是final的,这就是String值为什么不能被改变【可不是因为final class String的final呦】。这么做的好处是String对象可以在不同用户间共用,本身是线程安全的。

来看一个构造方法:

 public String(String original) {
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size) {
// 如果传入的original的存储char[]的长度大于要构造的字符串的长度,就copy original有效的那部分。
int off = original.offset;
v = Arrays.copyOfRange(originalValue, off, off+size);
} else {
// 因为传入的original的存储char[]的长度等于要构造的字符串的长度,所以无需copy
v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
}

  先说点题外的,笔者我第一次看到第2行的时候感到很诧异,String类的count属性不是private的吗。后来经过自己实验并反复回忆初学JAVA时老师对private的描述,private修饰的属性是只有在类的内部时才能被访问,于是恍然大悟。重新认识了private关键字,也是个收获。

  再来说这个构造方法,大概翻译了下JDK的注释。这里有两点,一是第8行的数组复制,里面是最终用的是System.arraycopy()方法,这个方法在JAVA中复制数组是最快的【这是个native方法,至于底层怎么实现就不太了解,猜测是通过内存里的地址吧】。再就是第11行的v = originalValue;,就是说,用于构造String的字符串,和构造出来的新的字符串,value属性是指向同一个char[]的【其实这也无所谓,因为String.value是final的】。

  关于字符集的构造方法这里就不说了,转换来转换去好烦。

  下面来看几个Sring类常用的方法的实现:

public boolean equals(Object anObject)

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

先比较hashCode,然后再遍历对比两个String的value值,没什么好说的。

public boolean startsWith(String prefix, int toffset)

public boolean startsWith(String prefix, int toffset) {
char ta[] = value;
int to = offset + toffset;
char pa[] = prefix.value;
int po = prefix.offset;
int pc = prefix.count;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > count - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}

也很简单,遍历字符串的value值。不过注释很有意思:-1>>>1。【-1的无符号右移1位,就是整数最大值,你懂得

public int indexOf(String str)

static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
} char first = target[targetOffset];
//遍历source时,如果剩下的字符比target还少,就没必要继续遍历了,就是这个max的作用。
int max = sourceOffset + (sourceCount - targetCount); for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* 找到匹配到的第一个字符 */
if (source[i] != first) {
while (++i <= max && source[i] != first);
} /* 匹配整个target是否完全匹配,如果不完全匹配并且source字符串还没遍历完就继续外层的for循环 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j] ==
target[k]; j++, k++); if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}

我们在使用这个方法时会有一个陷阱,就是sourceString.indexOf(""),传入空字符串会返回0。

public String substring(int beginIndex, int endIndex)

public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}

很简单,构造一个新的字符串。

  总之,这些常用的方法也不过是对char[]翻来覆去的操作。但提到String类就不得不提String Pool,这是JVM为了提高效率的一个缓存机制【至于JVM的这个原理,研究层次就更深入了,我想我以后会去了解吧】。目前要搞清楚String对象什么时候会在String Pool中建立,什么时候在Heap中建立,并将引用指向哪个,这个也是非常重要的。

String Pool(字符串池)

在创建字符串时,JVM的管理有如下特点:

  • 每当用任意方式创建一个String对象时,JVM都会String Pool中查找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串。
  • 在使用new关键字或包含变量的字符串拼接时,一定会在Heap中建立这个对象,并将引用指向这个对象。【同时仍然会维护String Pool】
  • 使用等号直接指定,或纯字符串拼接时,只会操作String Pool,如果有这个字符串则直接将引用指向,如果没有则创建后指向。
String temp = "ab";
String str1 = new String("abcd");
String str2 = "abcd";
String str3 = "ab" + "cd";
String str4 = temp + "cd"; System.out.println(str1 == str2);//false
System.out.println(str2 == str3);//true
System.out.println(str3 == str4);//false

有兴趣的可以自己试试,这里列举几个例子。

public native String intern();

  最后来说intern()。这是个native方法,它的执行结果是将字符串的引用指向String Pool中的对象。这样Heap中的对象就可以被回收掉了。

java.lang.StringBuffer

  我们经常需要String的变化,但String却是不可变的,那么只好串接后生成一个新的String对象来满足变化的需求。如果频繁的进行字符串拼接就会产生出大量的对象,这样很耗性能。所以这时,轮到StringBuffer来登场了。顾名思义,这是一个字符串缓冲区,可以随意变化。StringBuffer 上的主要操作是 append 和 insert 方法。

public synchronized StringBuffer append(String str)

 public synchronized StringBuffer append(String str) {
super.append(str);//父类方法如下
return this;
}
 public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
if (len == 0) return this;
int newCount = count + len;
if (newCount > value.length)
expandCapacity(newCount);//这个扩容的方法如下
str.getChars(0, len, value, count);
count = newCount;
return this;
}
 void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2;
if (newCapacity < 0) {
newCapacity = Integer.MAX_VALUE;
} else if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
value = Arrays.copyOf(value, newCapacity);
}

  从上面我们可以看出,一是如果拼接字符串时,变量为NULL,则按"null"来处理;二则是那个扩容。我们在构造一个StringBuffer时,如果不设置一个长度,则JDK会默认长度为16,如果我们最终拼接的字符串的长度远远超出这个长度,则StringBuffer会频繁的做扩容操作,所以,在new StringBuffer(),传入一个预估的长度参数是一个好习惯。

java.lang.StringBuilder

这个类与StringBuffer的区别就是synchronized,提高效率嘛,都懂得。笔者这里测试了一下,大概也就差1倍的速度。测试代码如下:

StringBuffer sb1 = new StringBuffer(1024);
StringBuilder sb2 = new StringBuilder(1024);
for(int i = 0; i < 10000000; i++){
//sb1.append("abc");
sb2.append("abc");
if((i & 1023) == 0){
//sb1 = new StringBuffer(1024);
sb2 = new StringBuilder(1024);
}
}

测试结果为:

StringBuffer平均耗时399ms

StringBuilder平均耗时254ms


好了,本次的学习就到这里吧。

学习是件快乐而又有成就感的事。

JAVA源码之JDK(三)——String、StringBuffer、StrinBuilder的更多相关文章

  1. Java源码赏析(三)初识 String 类

    由于String类比较复杂,现在采用多篇幅来讲述 这一期主要从String使用的关键字,实现的接口,属性以及覆盖的方法入手.省略了大部分的字符串操作,比如split().trim().replace( ...

  2. JAVA源码之JDK(二)——Integer、Long、Double

    这篇文章继续java.lang包下的源码学习,笔者也是找了几个比较常用的来阅读.下面针对Integer.Long.Double这样的基本类型的封装类,记录一些比较经典.常用的方法的学习心得,如toSt ...

  3. JAVA源码之JDK(一)——java.lang.Object

    想要深入学习JAVA,还需追本溯源,从源码学起.这是我目前的想法.如今JAVA各种开源框架涌出,很多JAVA程序员都只停留在如何熟练使用的层次.身为其中一员的我深感惭愧,所以要加快学习的脚步,开始研究 ...

  4. java源码学习(三)Enum

    Enum Enum类是java.lang包中一个类,他是Java语言中所有枚举类型的公共基类. 一.定义 public abstract class Enum<E extends Enum< ...

  5. Java源码赏析(五)再识 String 类

    在 Java源码赏析(三)初识 String 类   中,我们已经大概理解了String的接口,接下来我们描述一下String的常用工具方法. /** * 为了精简的String结构,之前提到的方法省 ...

  6. Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库

    http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...

  7. Java源码学习 -- java.lang.StringBuilder,java.lang.StringBuffer,java.lang.AbstractStringBuilder

    一直以来,都是看到网上说“ StringBuilder是线程不安全的,但运行效率高:StringBuffer 是线程安全的,但运行效率低”,然后默默记住:一个是线程安全.一个线程不安全,但对内在原因并 ...

  8. Java源码之String

    本文出自:http://blog.csdn.net/dt235201314/article/details/78330377 一丶概述 还记得那会的“Hello World”,第一个程序,输出的Str ...

  9. Java 源码刨析 - String

    [String 是如何实现的?它有哪些重要的方法?] String 内部实际存储结构为 char 数组,源码如下: public final class String implements java. ...

随机推荐

  1. osgi应用使用桥接的方式打成war包部署在websphere上时遇到的与cxf相关的问题

    原来我们的程序都是基于Equinox架构的,可是后面由于要实现打成war包在中间件中部署的需求,使用了eclipse官方提供的桥接方式实现. 桥接的部分后面有时间了我专门写一个文章来说,不明确的临时请 ...

  2. DS18B20 crc 算法

    http://blog.csdn.net/pengrui18/article/details/24740973 https://www.maximintegrated.com/cn/app-notes ...

  3. 监听用户的后退键,解决部分浏览器回退的bug

    监听用户的后退键,解决部分浏览器回退的bug $(document).keydown(function (event) { // 监听backspace键 if (event.keyCode == 8 ...

  4. Python zip Python zip函数

    zip([iterable, ...])zip()是Python的一个内建函数,它接受一系列可迭代的对象作为参数,将对象中对应的元素打包成一个个tuple(元组),然后返回由这些tuples组成的li ...

  5. laravel中,提交表单后给出提示例如添加成功,添加失败等等

    laravel中的表单插入,我想在表单插入成功后,可以像thinkphp一样可以有一个提示内容,上网Google,他们还是给出的方法就是 return redirect('/')->with(' ...

  6. 如何启动mininet实例上的wireshark图形界面

    启动wireshark 要启动mininet实例上的wireshark的图形界面,其实关键点只有两个: 保证宿主机上安装了X11 使用ssh -Y mininet@192.168.56.102 登录进 ...

  7. [css]后台管理系统布局

    知识点: 绝对定位+overflowhidden 整体思路 三大块 pg-header---需要固定 (height:48px) pg-content menu 右侧菜单-需要固定(width:200 ...

  8. nginx 查看访问 IP 并封禁 IP 详解

    1.查找服务器所有访问者ip方法: awk '{print $1}' nginx_access.log |sort |uniq -c|sort -n nginx.access.log 为nginx访问 ...

  9. iptables简单配置

    iptables简单配置 分类: Linux 安全2010-10-20 16:56 4241人阅读 评论(0) 收藏 举报 input防火墙tcpfilterubuntuservice # iptab ...

  10. ajax发送请求时为url添加参数(使用函数)

    <script> // ajax的get请求,使用函数向其url添加参数 function addURLParam(url,name,value){ url+=(url.indexOf(' ...