看到了以前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()的实现,回过头再来看这个问题。

  1. 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。

  1. ==的实现 
    大家都知道,java中保留了一些基本类型,比如int,float,是可以不已对象的形式保留在内存中的,数组也支持这些基础类型。而==是比较两个的两边如果是基础类型常量,也就是比较存在stack中常量引用是否指向常量池中相同大小的数值,如果==两边是基础类型变量,则是比较位于栈中的变量数值的是否相等。但是字符串比较奇怪,它是引用类型,但是在进行一些实验的时候又发现似乎具有一些基本类型的性质。这里我们来分析一下String类型的存储。

  2. 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)的更多相关文章

  1. String类原理分析及部分方法

    //String类原理分析及部分方法 //http://www.cnblogs.com/vamei/archive/2013/04/08/3000914.html //http://www.cnblo ...

  2. Java Reference核心原理分析

    本文转载自Java Reference核心原理分析 导语 带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用Byte ...

  3. Java 线程池原理分析

    1.简介 线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销.在应用上,线程池可应用在后端相关服务中.比如 Web 服务器,数据库服务器等 ...

  4. Java程序运行原理分析

    class文件内容 class文件包含Java程序执行的字节码 数据严格按照格式紧凑排列在class文件的二进制流,中间无分割符 文件开头有一个0xcafebabe(16进制)特殊的标志 JVM运行时 ...

  5. Java HashMap实现原理分析

    参考链接:https://www.cnblogs.com/xiarongjin/p/8310011.html 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是 ...

  6. 设计模式学习——JAVA动态代理原理分析

    一.JDK动态代理执行过程 上一篇我们讲了JDK动态代理的简单使用,今天我们就来研究一下它的原理. 首先我们回忆下上一篇的代码: public class Main { public static v ...

  7. Java反射实现原理分析

    目录: 一.反射的用法 二.反射实现原理 一.反射的用法 1.如何获取Class反射类 (1)通过getClass方法: Proxy proxy = new ProxyImpl(); Class pr ...

  8. Java 中 ConcurrentHashMap 原理分析

    一.Java并发基础 当一个对象或变量可以被多个线程共享的时候,就有可能使得程序的逻辑出现问题. 在一个对象中有一个变量i=0,有两个线程A,B都想对i加1,这个时候便有问题显现出来,关键就是对i加1 ...

  9. java线程启动原理分析

    一.前言不知道哪位古人说:人生三大境界.第一境界是:看山是山看水是水:第二境界是看山不是山看水不是水:第三境界:看山还是山看水还是水.其实我想对于任何一门技术的学习都是这样.形而上下者为之器,形而上者 ...

随机推荐

  1. 在Xcode中使用Git进行源码版本控制(转)

    http://www.cocoachina.com/ios/20140524/8536.html

  2. Jqurey实现相似EasyUI的页面布局

    截图例如以下:(可通过移动中间蓝色的条.来改变左右两边div的宽度) watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWFuZ21pbmd4aW5nOTgw ...

  3. 怎样利用JDBC启动Oracle 自己主动追踪(auto trace)

    有时我们须要对运行SQL的详细运行过程做一个追踪分析,特别是在应用程序性能优化的时候.Oracle两个工具能够帮助我们做好性能分析,一个是SQL_TRACE,一个是SESSION_EVENT.SQL_ ...

  4. CodeIgniter框架——CI的执行流程

    应用程序流程图 CodeIgniter执行流程 源码分析——CI到底做了些什么 (由welcome的例子出发——讲解index.php——讲解CodeIgniter.php) (load_class的 ...

  5. [Spring MVC]学习笔记--@Controller

    在讲解@Controller之前,先说明一下Spring MVC的官方文档在哪. 可能会有人和我一样,在刚接触Spring MVC时,发现在Spring的网站上找不到Spring MVC这个项目. 这 ...

  6. 3673: 可持久化并查集 by zky

    3673: 可持久化并查集 by zky Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 2170  Solved: 978[Submit][Status ...

  7. 怎样使用Chrome模拟手机浏览器測试移动端网站

    作者:zhanhailiang 日期:2014-10-10 环境说明: Chrome 37.0.2062.124 m 1. 通过[菜单→工具→开发人员工具|Javascript控制台]或[快捷键Ctr ...

  8. myBatis 课纲

    myBatis 课纲 第一章 MyBatis 架构.主要构件及相互关系 使用 MyBatis 构建项目 基本的增删改查映射文件方式(特殊符号处理),使用接口方式实现 结果集映射: 单个对象映射到Has ...

  9. (4.11)sql server内存使用

    一些内存使用错误理解   开篇小感悟 在实际的场景中会遇到各种奇怪的问题,为什么会感觉到奇怪,因为没有理论支撑的东西才感觉到奇怪,SQL Server自己管理内存,我们可以干预的方式也很少,所以日常很 ...

  10. 001-Java®语言规范、Java平台标准版文档、JVM概述

    一.概述 相关api地址:JDK10   JDK 9   JDK 8   JDK 7   JDK 6 Java语言和虚拟机规范: https://docs.oracle.com/javase/spec ...