看到了以前2016.5月学习java写的笔记,这里放在一起。
String实现的细节原理分析
一、jdk源码中String 的实现
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
上面是源码中String的一部分,从中可以得到几个重要的信息:
- 首先,String类是final的,所以不能被继承;
- 再者,String在内存中的实现,是以一个字符数组char[]实现的,所以是一个连续存储的区域,而且字符数组是final的,所以String对象一旦被创建就不能被修改的;
/**
* Initializes a newly created {@code String} object so that it represents
* an empty character sequence. Note that use of this constructor is
* unnecessary since Strings are immutable.
*/
public String() {
this.value = new char[0];
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
上面是String 的部分构造方法,从中可以看出,其实就是对String外壳内部的字符数组的操作,String就像是一美丽的外套一样。
注意第二个构造方法,this.value=original.value,仅仅是赋值引用给新的字符串!但是我们的理解新的字符串应该是以前字符串的一个完整的副本才对啊。这里其实是java做的一个优化,当新的字符串要进行删除或者其他操作是才会真正创建一个字符串并且将内存地址赋值给刚刚创建的引用。
二、String中 == 和 equals()的区别
前两天一个考试,遇到了非常坑爹的字符串比较相等的问题。原题大概是如下:
public class TestString {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String(s1);
System.out.println(s1==s2);
System.out.println(s3==s4);
System.out.println(s1==s3);
System.out.println(s3.equals(s4));
}
}
当时其他题目都可以搞定,唯独这一题纠结了好久,当时想要是机考可以编译运行跑出来多好,哈哈。
这里的正确答案应该是:
true,false,false,true
这里我们先放下这个问题,先探讨一下==的实现,以及equals()的实现,回过头再来看这个问题。
- equals()的实现
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
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;
}
由源代码之一知道,equals()首先比较是不是两个引用指向同一个对象,若是直接返回true;否则在带比较对象也是String类型的基础上比较串的长度,然后逐个比较字符。所以只要两个字符串内部的串,也就是字符序列是一样的,方法肯定返回true。
==的实现
大家都知道,java中保留了一些基本类型,比如int,float,是可以不已对象的形式保留在内存中的,数组也支持这些基础类型。而==是比较两个的两边如果是基础类型常量,也就是比较存在stack中常量引用是否指向常量池中相同大小的数值,如果==两边是基础类型变量,则是比较位于栈中的变量数值的是否相等。但是字符串比较奇怪,它是引用类型,但是在进行一些实验的时候又发现似乎具有一些基本类型的性质。这里我们来分析一下String类型的存储。-
String的存储
- 用常量字符串赋值给String引用
String s1 = "hello";
String s2 = "hello";
这里,首先编译期间,编译器检查到有字符串出现,就在常量池中查找,有没有引用指向堆中的一个String对象,并且该对象的字符序列等于“hello”,如果有则让s2也指向堆中同样的string对象,这就是为什么没有使用new符号的String对象也能使用String的各种方法了,应为常量池中不存在对象,所以从合格角度也说明字符串常量池中放的是字符串的引用;如果没有,就是s1创建的时候,就在堆中创建一个字符串对象,将引用放到常量池中,并且让s1指向堆中的对象s1。
String s3 = new String("hello");
String s4 = new String("hello");
注意这里是使用new来创建对象,是发生在运行期的事儿。当解释器发现有new的时候,果断的在堆中创建对象s3,并且让栈中的引用s3指向创建的对象,创建s4的时候也是这样,所以s3,s4指向堆中两个不同的对象,即使他们内容相同。
由上面的分析可知:
- 直接用一个字符串常量赋值给引用的s1和s2肯定是相等的,因为他们指向字符串常量池中相同的引用
- 而用new关键字生成的对象s3,s4肯定是s3!=s4的,因为他们指向heap中不同的对象,虽然这两个不同对象内部的字符数组是相等的
- s1,s3分别一个指向常量池,一个指向堆heap,肯定也是s1!=s3
- equals()的实现是在都是String对象基础上比较对象内部的内容,也就是字符数组
value[],所以肯定有s3.equals(s4)
三、从源代码中看不到的很多东西
字符串+的实现
public class Test {
public static void main(String[] args) {
String s1 = "hello" + "world";
String s2 = "good morning " + s1;
}
}
刚开始学习java从书上看到,java中不允许运算符重载机制,不想C++那样*。但是看到String这种引用类型都这样玩的时候我就慌了。后来知道可以去源码中看String的实现,也没有找着具体是怎么实现的。最近看了一些牛人的博客,才知道了一点点“内幕”。
编译之后在命令行中输入
javap -c Test
就会执行反编译,得到如下的JVM的汇编代码:
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hellohello
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String good morning
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: return
}
看起来很复杂哈,不用掌握汇编语言,只用看右边的注释,可以发现加载了StringBuilder类,而且调用了两次append()方法。这就是编译器在搞鬼了。
java知道String的拼接太耗内存和时间,所以在这里默认的帮我们生成StringBuilder,将字符串连接好了之后,调用toString()生成字符串赋值给目标的引用就行了。合理的两个append()方法分别是为两个+操作符调用的。
然而这些在java类库的源码中永远找不到。
四、求教大牛解答
- java常量池中存放的到底是什么?
- 就拿字符串常量池来说,如果存放的是字符串常量,那为什么栈中的引用指向这个常量,还可以使用String的各种方法呢?难道是复制常量到堆中对象?岂不是很浪费吗?如果是存放引用的话,通过引用查找是否有相同字符串存在,开销好像也是很大的…
- 希望有大牛给予赐教,不甚感激。
java string 细节原理分析(2016.5)的更多相关文章
-
String类原理分析及部分方法
//String类原理分析及部分方法 //http://www.cnblogs.com/vamei/archive/2013/04/08/3000914.html //http://www.cnblo ...
-
Java Reference核心原理分析
本文转载自Java Reference核心原理分析 导语 带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用Byte ...
-
Java 线程池原理分析
1.简介 线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销.在应用上,线程池可应用在后端相关服务中.比如 Web 服务器,数据库服务器等 ...
-
Java程序运行原理分析
class文件内容 class文件包含Java程序执行的字节码 数据严格按照格式紧凑排列在class文件的二进制流,中间无分割符 文件开头有一个0xcafebabe(16进制)特殊的标志 JVM运行时 ...
-
Java HashMap实现原理分析
参考链接:https://www.cnblogs.com/xiarongjin/p/8310011.html 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是 ...
-
设计模式学习——JAVA动态代理原理分析
一.JDK动态代理执行过程 上一篇我们讲了JDK动态代理的简单使用,今天我们就来研究一下它的原理. 首先我们回忆下上一篇的代码: public class Main { public static v ...
-
Java反射实现原理分析
目录: 一.反射的用法 二.反射实现原理 一.反射的用法 1.如何获取Class反射类 (1)通过getClass方法: Proxy proxy = new ProxyImpl(); Class pr ...
-
Java 中 ConcurrentHashMap 原理分析
一.Java并发基础 当一个对象或变量可以被多个线程共享的时候,就有可能使得程序的逻辑出现问题. 在一个对象中有一个变量i=0,有两个线程A,B都想对i加1,这个时候便有问题显现出来,关键就是对i加1 ...
-
java线程启动原理分析
一.前言不知道哪位古人说:人生三大境界.第一境界是:看山是山看水是水:第二境界是看山不是山看水不是水:第三境界:看山还是山看水还是水.其实我想对于任何一门技术的学习都是这样.形而上下者为之器,形而上者 ...
随机推荐
-
我的git学习
当遇到不想commit的,而status已经现实出来了,可以使用 git rm -r --cached "fine name or 文件夹" 出现 Git – fatal: U ...
-
xampp 配置虚拟主机
1.安装好xampp后 2.找到安装目录 apache目录--conf目录--extra目录--httpd-vhosts.conf文件(用记事本或者别的编辑器打开) 文件最后添加代码 <Virt ...
-
CSS应用给网页元素的几种方式总结
一.内联式样式表 直接在HTML标签中使用style进行定义样式.如:<p style="color:red;">这里是红色文字</p>. 二.嵌入式样式表 ...
-
POJ 2318 TOYS &;&; POJ 2398 Toy Storage(几何)
2318 TOYS 2398 Toy Storage 题意 : 给你n块板的坐标,m个玩具的具体坐标,2318中板是有序的,而2398无序需要自己排序,2318要求输出的是每个区间内的玩具数,而231 ...
-
[]T 还是 []*T, 这是一个问题
全面分析Go语言中的类型和类型指针的抉择 目录 [−] 副本的创建 T的副本创建 *T的副本创建 如何选择 T 和 *T 什么时候发生副本创建 最常见的case map.slice和数组 for-ra ...
-
机器学习——交叉验证,GridSearchCV,岭回归
0.交叉验证 交叉验证的基本思想是把在某种意义下将原始数据(dataset)进行分组,一部分做为训练集(train set),另一部分做为验证集(validation set or test set) ...
-
Golang源码探索(二) 协程的实现原理(转)
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱,虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底 ...
-
Android Studio提示 Connection reset
解决步骤: 1:Android studio开发工具:File -> Invalidate caches / Restart:选择Invalidate and Restart关闭 Android ...
-
数据库简单练习 建表+select
create table student ( sno int primary key, sname char(20), sex char(2), birthday datetime, class i ...
-
转:nginx基础概念(connection)
在nginx中connection就是对tcp连接的封装,其中包括连接的socket,读事件,写事件.利用nginx封装的connection,我们可以很方便的使用nginx来处理与连接相关的事情,比 ...