阅读Java的String类与StringBuffer类源程序
来源:互联网 发布:php禁止查看源代码 编辑:程序博客网 时间:2024/05/18 08:15
注:读的是比较早的一个版本。status updated to 1.4
关于char和byte
在Sting类中第一个引起我注意的点是publicString(byte[] ascii, int hibyte, int offset, int count)函数。这个函数将一个byte[]数组的字符串,每个byte前面加上8bits的hibyte前缀,形成一个新的String,而String中存储用的是char[]数组。也就是说,Java中的char是两个字节的。
可以猜测,char这么设计是为了支持unicode等两个字节的码,也就是说一个char可以是一个汉字。经实际测试,让a为‘你’,发现确实如此。
虽然是两个字节,却不能存下两个普通的英文字母。经测试,让b为‘ni’,出现提示 “Invalid character constant”。也就是说如果放英文字母的话,char里也只能放一个。
关于编码
前面所说的public String(byte[] ascii, inthibyte, int offset, int count),及其简略版publicString(byte[] ascii, int hibyte)是已经deprecated的方法。我认为原因是:一般人们没有必要去手动设置高8位的值,而希望采用更智能的方式——输入编码格式。
因此,推荐使用的是方法public String(byte[] data, intoffset, int count, String encoding),及其简略版publicString(byte[] data, String encoding)。在这个函数的内部,是使用CharsetDecoder来实现的。
查文档,charset大概有如下几种。
Charset
描述
US-ASCII
7 位 ASCII 字符,也叫作 ISO646-US、Unicode 字符集的基本拉丁块
ISO-8859-1
ISO 拉丁字母表 No.1,也叫作 ISO-LATIN-1
UTF-8
8 位 UCS 转换格式
UTF-16BE
16 位 UCS 转换格式,Big Endian(最低地址存放高位字节)字节顺序
UTF-16LE
16 位 UCS 转换格式,Little-endian(最高地址存放低位字节)字节顺序
UTF-16
16 位 UCS 转换格式,字节顺序由可选的字节顺序标记来标识
方法public String(byte[] data, int offset,int count)和简略版public String(byte[] data)是在没有encoding的情况下将byte生成String。事实上使用的encoding是环境默认的编码方式,通过System.getProperty("file.encoding")获得。
如果将char[]转成byte[]没成功,会进入安全模式('safe' encoding)。每个char如果高的那个字节全为0的话,也就是ASCII字符,直接将char转成byte。如果高位不全为0的话,转成字符 ‘?’,这或许就是我们在看一些网站的时候中文乱码为 ‘?’的原因吧。更常见的
关于大小写
在equalsIgnoreCase方法中,判断不考虑大小写下两个字母是否相等时,用的判断条件是(c1 != c2 && Character.toUpperCase(c1) !=Character.toUpperCase(c2) && Character.toLowerCase(c1) != Character.toLowerCase(c2))。
其实后两个判断就够用了,为什么要累赘的c1!=c2的比较呢?注释中说的很明白:Note that checking c1 != c2 is redundant, but avoids method calls。因为一般来说还是大小写一样的情况比较多,先判断c1!=c2可以尽可能避免调用toUpperCase等方法,从而提高效率。这我是可以理解并认同的。
但是——难道前两个条件还不够吗?把c1和c2都转成了UpperCase,难道还不足以判别是否相等吗?为什么还要写第三条判断呢?
另外,在转化大小写的时候对于土耳其语做了特殊判断。一般来说,字符“i”(\u0069) 是字符“I”(\u0049)的小写版本。 但是,土耳其语中有“带点的 I”字符“İ”(\u0130),该字符是“i”的大写版本。 土耳其语还包括小写的“不带点的 i”字符“ı”(\u0131),该字符的大写形式才是“I”。 (参考http://msdn.microsoft.com/zh-cn/library/azure/dd465121.aspx?cs-save-lang=1&cs-lang=vb)
关于ensureCapacity
StringBuffer中,最值得一看的就是ensureCapacity方法了。
ensureCapacity_unsynchronized方法代码如下:
private void ensureCapacity_unsynchronized(int minimumCapacity) { if (shared || minimumCapacity > value.length) { int max = (minimumCapacity > value.length ? value.length * 2 + 2 : value.length); minimumCapacity = (minimumCapacity < max ? max : minimumCapacity); char[] nb = new char[minimumCapacity]; VMSystem.arraycopy(value, 0, nb, 0, count); value = nb; shared = false; } }
先抛开share不看,要做的主要事情是确保大小。如果设定值minimumCapacity比原来的length小(或相等)的话就用原来的。如果大的话,就用length * 2 + 2和minimumCapacity中比较大的那个。新建一个这么大的char[]数组,把原来的值拷贝进去,把StringBuffer的value换成这个数组。
为什么不直接采用minimumCapacity,而还要考虑length * 2 + 2呢?我想这是种用空间换时间的策略。如果只是吧空间升级成了minimumCapacity,下次可能还要升级。而使用length*2+2的话,下次别增长太多是不会超的。
每次扩张到两倍,这是有道理的。以前数据结构讲hash的时候提到过类似的思想,如果一个哈希表满了,就开一个两倍的再复制进去,这样的扩展策略均摊复杂度是O(n)的,还可以接受。至于加2嘛,应该是避免append时新加的String差不多和原来的Stirng差不多长(多一两个字符)出现的麻烦吧。
再说这个share的问题。如果之前这个StringBuffer已经是share的话,一旦调用这个方法就会变成非share的。我推测,这么做应该是基于这个考虑——
被share的StringBuffer有改动有可能导致一些指向它的String变化。这是不允许的。因此,在StringBuffer进行一些会改变内容的操作时(setCharAt),会调用方法ensureCapacity_unsynchronized。这不是真的为了确保空间,而是要与share的String分开来。
其他小细节
l 很多时候函数重载不用重新写代码,只要把某些参数值补上,调用另一个函数即可。这个实践符合Don’t repeat yourself原则。
l 调用getBytes时是返回byte[]的,而getChars是在传入的变量中指定位置的,要返回char[]得用toCharArray。
l 计算hashCode可能被认为比较费时,因此将计算过的值缓存在cachedHashCode中。cachedHashCode为0被认为没有计算过hashCode,即使hashCode真为0也重新计算(反正不费时间)
l indexOf方法的String版本的实现居然是枚举fromIndex,一个个比较regionMatches。为什么不用KMP算法呢?(看新版本貌似改了)
l substring方法中子串如果达到1/4原长就用don’t copy模式了。
l 以前以为trim的时候是去掉两头的空格,现在发现还有两头ascii码小于空格的那些控制符。
l 在String中valueOf是静态函数,可以用于各种类型的数据变成字符。
l intern 方法针对的是一个字符串池。如果池里有一样的字符串,则返回池里的字符串。不然就把String加到池里,返回其引用。
l 可以使用append扩充StringBuffer,而不需要新建一个。
l 使用synchronized来确保线程安全。改动StringBuffer部分用synchronized(){}括起来,避免多个线程同时使用。有点类似于操作系统中的signal信号量。
String与StringBuffer的相互使用
String可以通过StringBuffer来构造。如果StringBuffer中的字符串长度在buffer总长度的1/4以内,使用VMSystem.arraycopy构造新的String。如果比1/4长的话,直接分享,让StringBuffer的share变量为true,把StringBuffer的char[]数组赋给String,类似于传了指针。
StringBuffer的substring方法可返回String,如果子串有1/4长的话就让share变true。构造String时根据share与否,决定是拷贝还是传指针。(String的substring方法也是类似)
具体在String中的构造函数是这样的:
</pre><pre name="code" class="java"> private voidensureCapacity_unsynchronized(int minimumCapacity) { if (shared || minimumCapacity >value.length) { int max = (minimumCapacity >value.length ? value.length * 2 + 2 : value.length); minimumCapacity = (minimumCapacity <max ? max : minimumCapacity); char[] nb = new char[minimumCapacity]; VMSystem.arraycopy(value, 0, nb, 0,count); value = nb; shared = false; } }
通过这里value=data这样的做法,实际传的是指针。Java里没有指针的说法,可以认为传递的是char[]这个对象。在Java中对象的传递不是拷贝的,而仅仅是引用。
String总结
String类本身是final的。在我的理解中这里的final有点类似于C/C++里的const,也就是String一旦创建就改不了了。所以可以看到,对String进行处理的操作,其实都是返回一个新的String,而非对原来的String类进行操作。String的接口有Serializable, Comparable, CharSequence。
String中非static的变量有四个:
l final char[] value;
l final int count;
l private int cachedHashCode;
l final int offset;
也就是说除了比较的字符串中的值以外,每个string还要存储三个int。虽然不像C那么对空间锱铢必较,但浪费12个字节还算能接受。
StringBuffer总结
StringBuffer类本身是final的,接口有Serializable, CharSequence。非final的有
l int count;
l char[] value;
l boolean shared;
StringBuffer类的append,insert,reverse方法是String类没有的。而一些跟正则表达式有关的匹配方法如split,replaceAll,replaceFirst等方法是StringBuffer类没有的。
String和StringBuffer的区别总结
l StringBuffer是线程安全的,而String不是。使用多线程的时候还用StringBuffer比较好。
l String中的char[]的余量主要在offset处,而一般offset为0,char的大小就是要保存的字符串的大小。当要做一些操作时,都要新构造一个String。StringBuffer的char[]大小有比较大的余量,做一些操作时如果新串的长度没有超过空间,就可以直接在原来的数组上做的。StringBuffer比较适合空间比较充裕,而希望效率高的情况。
- 阅读Java的String类与StringBuffer类源程序
- JAVA的StringBuffer类(与String)
- JAVA 的StringBuffer与String类的一些区别
- Java String类与StringBuffer类的区别
- 个人理解Java中String与StringBuffer类的区别
- java string类与stringbuffer类
- java-类与对象 String StringBuffer
- String 与 StringBuffer类
- JAVA的StringBuffer类--------StringBuffer与String的区别及用法
- String类与StringBuffer类的研究
- String 与 StringBuffer类 的区别
- JAVA的StringBuffer类和String类
- JAVA的StringBuffer类和String比较
- Java-String与StringBuffer
- Java String与StringBuffer
- String 类与 StringBuffer 类
- String 与 StringBuffer类(转贴)
- JAVA中String与StringBuffer的区别
- SQL总结(二)连表查询
- GIT学习笔记
- timus 1993. This cheeseburger you don't need
- jQuery折叠菜单
- SQL总结(三)其他查询
- 阅读Java的String类与StringBuffer类源程序
- Android大图片裁剪终极解决方案
- Garlands - UVa 1443 二分+dp
- OC基础晋级阶段里面还是比较详细,适合自学的朋友
- STL源码剖析---关联容器
- 浏览器对象模型BOM
- 贪心--Wooden Sticks
- SQL总结(四)编辑类
- Neuroph studio 入门教程