String是我们接触最多的类,无论是学习中还是工作中,基本每天都会和字符串打交道,从字符串本身的各种拼接、切片、变形,再到和其他基本数据类型的转换,几乎无时无刻都在使用它,今天就让我们揭开String神秘的面纱,这一小节主要讲解String的源代码是怎么构建的,下一节是String的一些疑点难点,通常在面试中会被问到。

  在学习String之前,让我们先简单的看一下JVM的内存模型,附图一张,来自百度百科:

在java的每一个通过编译生成字节码文件后,运行期JVM将字节码文件(也就是class文件)的对象通过类加载器加载到JVM运行区,如上图,通过线程独享和共享分为俩部分,其中线程共享区包括方法区和java堆,线程独享区包括虚拟机栈、本地方法栈和程序计数器

我们常说的基本类型在栈中,引用类型在堆中,其中的堆就是java堆(线程共享),栈就是虚拟机栈(线程独享),而String还涉及到方法区,因为String对象是常量,因为String其实就是char数组,而这个数组被final修饰了,详情在源代码中。

  1. /*
  2. *
  3. * 在java程序中,所有字符串文字都是String的实例,字符串是不变的; 它们的值在创建后无法更改。
  4. * 字符串缓冲区(StringBuffer和StringBuild)支持可变字符串。因为String对象是不可变的,所以它们可以共享。
  5. * 例如:
  6. * String str =“abc”;
  7. * 相当于:
  8. * char data [] = {'a','b','c'};
  9. * String str = new String(data);
  10. * String对象并不是简单的存在于堆内存中,而是在方法区的常量池中,我会在下一节详细讲解
  11. *
  12. */
  13.  
  14. public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
  15. /*
  16. * 该值用于字符存储。到这里就为什么String字符串会在常量池中我想大家一定豁然开朗了,因为final修饰的就是常量,而且不可修改,也就是说当我们定义一个
  17. * String a = "gaoxiaolong"
  18. * 从你的程序开始跑到最后结束运行,这个"gaoxiaolong"都一直存在,当然如果被当成垃圾回收那就另当别论了。而我们可以改变的仅仅是这个String的引用a的指向:
  19. * a = "gollong",这是a的指向变了,但是"gaoxiaolong"仍然在常量池中,如果再有一个b引用,String b = "gaoxiaolong",这时这个"gaoxiaolong"
  20. * 就不用再生成了,因为已经存在了。当然如果在这期间"gaoxiaolong"已经被回收就不是这么一回事了。
  21. */
  22. private final char value[];
  23.  
  24. /*
  25. * 缓存字符串的哈希码,默认为0
  26. */
  27. private int hash;
  28.  
  29. /*
  30. * 实现Serializable(序列化)接口生成的序列化UID,用于序列化和反序列化识别此对象是String对象, 我会用一章来讲解序列化和反序列化
  31. */
  32. private static final long serialVersionUID = -6849794470754667710L;
  33.  
  34. /*
  35. * Java.io.ObjectStreamField类是可序列化字段来自Serializable类的描述。 ObjectStreamFields数组用来声明一个类的序列化字段。
  36. * 在序列化和反序列化中详细详细讲解
  37. */
  38. private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
  39.  
  40. /*
  41. * 初始化新创建的String对象,使其表示空字符序列。 请注意,由于字符串是不可变的,因此不必使用此构造函数。
  42. */
  43. public String() {
  44. this.value = "".value;
  45. }
  46.  
  47. /*
  48. * 初始化新创建的String对象,使其表示与参数相同的字符序列;
  49. * 换句话说,新创建的字符串是参数字符串的副本。 除非需要original的明确副本,否则不必使用此构造函数,因为字符串是不可变的。
  50. * 可见官方极力推崇String a = "gaoxiaolong"的写法,这种构造器仅仅是创建一个副本。其实这个副本是在堆内存中的一个引用指向方法区中的真正的"gaoxiaolong"
  51. * 他们还是同一个"gaoxiaolong",只不过栈当中的地址值不同罢了
  52. */
  53. public String(String original) {
  54. this.value = original.value;
  55. this.hash = original.hash;
  56. }
  57.  
  58. /*
  59. * 比较常用的一种构造方法,可以把一个字符数组转化为字符串,其实就是将这个字符数组赋值给当前引用的char[] value属性
  60. * 分配新的String,使其表示当前包含在字符数组参数中的字符序列。 复制字符数组的内容; 后续修改字符数组不会影响新创建的字符串。
  61. * 内部调用 Arrays工具类中的copyOf方法
  62. */
  63. public String(char value[]) {
  64. this.value = Arrays.copyOf(value, value.length);
  65. }
  66. /*
  67. * 这个构造器看源码我们可以看到将char数组直接赋值给当前对象的value属性,区别于上一个构造器,上一个是完全复制一份数组,
  68. * 相互之间不影响,而这个是俩个引用指向同一个数组,一个被改变另一个也会被改变。
  69. * 但是这个构造器的访问权限是包访问权限,所以我们无法使用。不必纠结
  70. */
  71. String(char[] value, boolean share) {
  72. // assert share : "unshared not supported";
  73. this.value = value;
  74. }
  75. /*
  76. * 分配一个新的String,其中包含字符数组参数的子数组中的字符。offset参数是子数组的第一个字符的索引,
  77. * count参数指定子数组的长度。 复制子阵列的内容; 后续修改字符数组不会影响新创建的字符串。
  78. * 从源码中我们可以看出一种特殊情况:如果count = 0且offset <= value.length那么实际上就是一个空字符串
  79. * 这里一定要记住,第三个参数是个数,也就是长度,千万不要记成结束位置,切记切记!
  80. * 这一点比较特殊,在字符串构造器中,都是offset和count参数,而在切片函数substring(int beginIndex, int endIndex)中
  81. * 所需要的参数却是开始位置和结束位置,一定不要记混了。
  82. */
  83. public String(char value[], int offset, int count) {
  84. if (offset < 0) {
  85. throw new StringIndexOutOfBoundsException(offset);
  86. }
  87. if (count <= 0) {
  88. if (count < 0) {
  89. throw new StringIndexOutOfBoundsException(count);
  90. }
  91. if (offset <= value.length) {
  92. this.value = "".value;
  93. return;
  94. }
  95. }
  96. // 如果offset+count的值超过的数组的长度自然报错,如果错误信息返回的是offset+count的值,则是这个错误。
  97. if (offset > value.length - count) {
  98. throw new StringIndexOutOfBoundsException(offset + count);
  99. }
  100. this.value = Arrays.copyOfRange(value, offset, offset + count);
  101. }
  102.  
  103. /*
  104. *
  105. */
  106. public String(int[] codePoints, int offset, int count) {
  107. if (offset < 0) {
  108. throw new StringIndexOutOfBoundsException(offset);
  109. }
  110. if (count <= 0) {
  111. if (count < 0) {
  112. throw new StringIndexOutOfBoundsException(count);
  113. }
  114. if (offset <= codePoints.length) {
  115. this.value = "".value;
  116. return;
  117. }
  118. }
  119. if (offset > codePoints.length - count) {
  120. throw new StringIndexOutOfBoundsException(offset + count);
  121. }
  122.  
  123. final int end = offset + count;
  124.  
  125. // Pass 1: Compute precise size of char[]
  126. int n = count;
  127. for (int i = offset; i < end; i++) {
  128. int c = codePoints[i];
  129. if (Character.isBmpCodePoint(c))
  130. continue;
  131. else if (Character.isValidCodePoint(c))
  132. n++;
  133. else
  134. throw new IllegalArgumentException(Integer.toString(c));
  135. }
  136.  
  137. // Pass 2: Allocate and fill in char[]
  138. final char[] v = new char[n];
  139.  
  140. for (int i = offset, j = 0; i < end; i++, j++) {
  141. int c = codePoints[i];
  142. if (Character.isBmpCodePoint(c))
  143. v[j] = (char) c;
  144. else
  145. Character.toSurrogates(c, v, j++);
  146. }
  147.  
  148. this.value = v;
  149. }
  150. /*
  151. * 检查边界的一个函数,在上面将字符数组转换为String对象时,我们见过了函数内的边界判断,由于关于字节的构造器过多,所以讲反复重用的代码封装成一个函数
  152. * 到时候直接调用即可,这就是面向对象中封装的思想。
  153. */
  154. private static void checkBounds(byte[] bytes, int offset, int length) {
  155. if (length < 0)
  156. throw new StringIndexOutOfBoundsException(length);
  157. if (offset < 0)
  158. throw new StringIndexOutOfBoundsException(offset);
  159. if (offset > bytes.length - length)
  160. throw new StringIndexOutOfBoundsException(offset + length);
  161. }
  162. /*
  163. *
  164. */
  165. @Deprecated
  166. public String(byte ascii[], int hibyte) {
  167. this(ascii, hibyte, 0, ascii.length);
  168. }
  169.  
  170. /*
  171. * @Deprecated注解表示此方法已经过时,但仍然可以使用,只是不推荐
  172. * ascii为要转换为字符的字节数组,hibyte为每个16位Unicode代码单元的前8位
  173. * java使用的是Unicode编码表,每个字符都占俩个字节,但是一个字节只是一个字节,所以此方法在转换的时候
  174. * 想指定另一个字节的数据来达到不同的需求,但是往往是不正确的转换,而使用指定charset完全可以达到此目的。
  175. */
  176. @Deprecated
  177. public String(byte ascii[], int hibyte, int offset, int count) {
  178. checkBounds(ascii, offset, count);
  179. char value[] = new char[count];
  180.  
  181. if (hibyte == 0) {
  182. for (int i = count; i-- > 0;) {
  183. value[i] = (char) (ascii[i + offset] & 0xff);
  184. }
  185. } else {
  186. hibyte <<= 8;
  187. for (int i = count; i-- > 0;) {
  188. value[i] = (char) (hibyte | (ascii[i + offset] & 0xff));
  189. }
  190. }
  191. this.value = value;
  192. }
  193.  
  194. /*
  195. * 下面的都是讲字节数组转换为字符串的构造器,也是方法的重载,为了适应不用的转换需求,不许参数为字节数组,
  196. * 可变参数为开始位置offset,转换长度length,指定编码方式charset,其中charset有俩种指定方式,一种是出入String类型的charsetName
  197. * 另一种是传入Charset类型的charset,如果不知道的话,offset默认为0,length默认为字节数组的长度。chatset默认为本地编码
  198. */
  199. public String(byte bytes[]) {
  200. this(bytes, 0, bytes.length);
  201. }
  202.  
  203. /*
  204. *
  205. */
  206. public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException {
  207. if (charsetName == null)
  208. throw new NullPointerException("charsetName");
  209. checkBounds(bytes, offset, length);
  210. this.value = StringCoding.decode(charsetName, bytes, offset, length);
  211. }
  212.  
  213. /*
  214. *
  215. */
  216. public String(byte bytes[], int offset, int length, Charset charset) {
  217. if (charset == null)
  218. throw new NullPointerException("charset");
  219. checkBounds(bytes, offset, length);
  220. this.value = StringCoding.decode(charset, bytes, offset, length);
  221. }
  222.  
  223. /*
  224. */
  225. public String(byte bytes[], String charsetName) throws UnsupportedEncodingException {
  226. this(bytes, 0, bytes.length, charsetName);
  227. }
  228.  
  229. /*
  230. *
  231. */
  232. public String(byte bytes[], Charset charset) {
  233. this(bytes, 0, bytes.length, charset);
  234. }
  235.  
  236. /*
  237. * 将字节数组转换为字符串,指定开始位置和长度。
  238. */
  239. public String(byte bytes[], int offset, int length) {
  240. checkBounds(bytes, offset, length);
  241. this.value = StringCoding.decode(bytes, offset, length);
  242. }
  243.  
  244. /*
  245. * 将字符串缓冲区对象转换成String对象,实际上就是将一个堆内存中的对象写入常量池中,
  246. * 使用Arrays.copyOf复制,得到的是俩份完全独立的数组
  247. * StringBuilder是线程不安全的,效率高
  248. * StringBuffer是线程安全的,但代价就是效率低,效率低是相比于StringBuilder,它仍然比String快很多
  249. */
  250. public String(StringBuffer buffer) {
  251. synchronized (buffer) {
  252. this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
  253. }
  254. }
  255. public String(StringBuilder builder) {
  256. this.value = Arrays.copyOf(builder.getValue(), builder.length());
  257. }
  258. }

java源码解析之String类(一)的更多相关文章

  1. java源码解析之String类(三)

    上一节我们主要讲了String类的一些不是很常用的方法,其中需要掌握的如下,我就不再赘述了 public int length() public boolean isEmpty() public by ...

  2. java源码解析之String类(二)

    上一节主要介绍了String类的一些构造方法,主要分为四类 无参构造器:String(),创建一个空字符串"",区别于null字符串,""已经初始化,null并 ...

  3. java源码解析之String类(四)

    /* * 返回指定字符第一次出现的字符串内的索引 */ public int indexOf(int ch) { return indexOf(ch, 0); } /* * 返回指定字符第一次出现的字 ...

  4. java源码解析之String类(五)

    /* * 切片函数,非常重要,这里一定要牢记beginIndex是开始位置,endIndex是结束位置,区别于以前学的offset是开始位置,而count或length是个数和长度 * 比如说,new ...

  5. java源码解析之Object类

    一.Object类概述   Object类是java中类层次的根,是所有类的基类.在编译时会自动导入.Object中的方法如下: 二.方法详解   Object的方法可以分成两类,一类是被关键字fin ...

  6. [Java源码解析] -- String类的compareTo(String otherString)方法的源码解析

    String类下的compareTo(String otherString)方法的源码解析 一. 前言 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识,  ...

  7. 【Java源码解析】Thread

    简介 线程本质上也是进程.线程机制提供了在同一程序内共享内存地址空间运行的一组线程.对于内核来讲,它就是进程,只是该进程和其他一下进程共享某些资源,比如地址空间.在Java语言里,Thread类封装了 ...

  8. AOP源码解析:AspectJAwareAdvisorAutoProxyCreator类的介绍

    AspectJAwareAdvisorAutoProxyCreator 的类图 上图中一些 类/接口 的介绍: AspectJAwareAdvisorAutoProxyCreator : 公开了Asp ...

  9. [java源码解析]对HashMap源码的分析(二)

    上文我们讲了HashMap那骚骚的逻辑结构,这一篇我们来吹吹它的实现思想,也就是算法层面.有兴趣看下或者回顾上一篇HashMap逻辑层面的,可以看下HashMap源码解析(一).使用了哈希表得“拉链法 ...

随机推荐

  1. WebView 联系(要么button)至 Activity 跳跃在几个方面

    第一 ,写一个 JavaScriptinterface 分类.内实现WebView至Activity 页面跳转 public class JavaScriptinterface { Activity ...

  2. Codeforces Round #248 (Div. 2) B称号 【数据结构:树状数组】

    主题链接:http://codeforces.com/contest/433/problem/B 题目大意:给n(1 ≤ n ≤ 105)个数据(1 ≤ vi ≤ 109),当中有m(1 ≤ m ≤  ...

  3. python3使用Lxml库操作XPath

    download address: http://pypi.python.org/pypi/lxml/2.3 lxml is a Pythonic, mature binding for the li ...

  4. 微信小程序开发之从相册获取图片 使用相机拍照 本地图片上传

    1.index.wxml <!--index.wxml--> <button style="margin:30rpx;" bindtap="choose ...

  5. WPF 4 DataGrid 控件(自定义样式篇)

    原文:WPF 4 DataGrid 控件(自定义样式篇)      在<WPF 4 DataGrid 控件(基本功能篇)>中我们已经学习了DataGrid 的基本功能及使用方法.本篇将继续 ...

  6. 用MVVM模式开发中遇到的零散问题总结(4)——自制摄像头拍摄大头贴控件

    原文:用MVVM模式开发中遇到的零散问题总结(4)--自制摄像头拍摄大头贴控件 一直有个疑问,为什么silverlight对摄像头支持这么好,WPF却一个库都没有....于是我各种苦恼啊,各种Code ...

  7. js 评分

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  8. python3. 报“ImportError: No module named ‘MySQLdb'”

    需要安装PyMysql pip install PyMysql 然后在程序中引入 import pymysql pymysql.install_as_MySQLdb() app.config['SQL ...

  9. TThreadList Demo

    type TForm1 = class(TForm) Button1: TButton; Button3: TButton; ListBox1: TListBox; Button2: TButton; ...

  10. String内存结构

    var s: AnsiString; begin s := '1234567890'; showmessage(s); end; 变量s的内存结构为A8 03 01 00 FF FF FF FF 0A ...