看到了以前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. Laragon集成开发环境+配置Xdebug+postman运行Xdebug

    [ Laravel 5.5 文档 ] 快速入门 —— 使用 Laragon 在 Windows 中搭建 Laravel 开发环境:http://laravelacademy.org/post/7754 ...

  2. PHP 代码简洁之道 ( PHP Clean Code)

    https://laravel-china.org/topics/7774/the-conciseness-of-the-php-code-php-clean-code

  3. 那些可爱的 Linux 命令

    环境 root@15b883:~# uname -a ##需要是Ubuntu环境 Linux 15b883 --generic #- :: UTC x86_64 x86_64 x86_64 GNU/L ...

  4. LUA速成教程

    說明: 1.該教程適合對編程有一定了解的人員. 2.該教程在WINDOWS下實驗. 切入正題, 1.首先下載Notepad++,工欲善其事,必先利其器,然後安裝NotePad++的插件NppExec. ...

  5. 编写自己的ls命令

    ····要编写ls命令,首先要了解它能做什么,完成了什么工作,是如何完成这些工作的····  一.ls命令能做什么? 我们在命令行输入ls,ls默认找出当前目录中所有文件的文件名,并且按照字典序排序后 ...

  6. C语言8.3冒泡排序

    8.3.1 例8-5 题目:输入n个正整数,将他们从小到大排序后输出,要求使用冒泡排序法. 而在自己抄写代码的时候,出现了以下问题: # include<stdio.h> void bub ...

  7. General Decimal Arithmetic 浮点算法

    General Decimal Arithmetic http://speleotrove.com/decimal/ General Decimal Arithmetic [ FAQ | Decima ...

  8. 基于apache —HttpClient的小爬虫获取网页内容

    今天(17-03-31)忙了一下午研究webmagic,发现自己还太年轻,对于这样难度的框架(类库) 还是难以接受,还是从基础开始吧,因为相对基础的东西教程相多一些,于是乎我找了apache其下的 H ...

  9. 简单的 ajax demo

    2.最重要也是最核心的是要自己改下bootstrap-paginator.js源文件,如下: [javascript] view plain copy      function oneferRepo ...

  10. h5-localStorage实现缓存ajax请求数据

    使用背景:要实现每次鼠标hover“能力雷达”,则显示能力雷达图(通过ajax请求数据实现雷达图数据显示),所以每次hover都去请求ajax会影响性能,因此这里要用到本地缓存. 实现: 此处是通过传 ...