前言

public class Test {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a==b);
System.out.println(a.equals(b));
System.out.println(a==c);
System.out.println(a.equals(c));
}
}

那么上段代码的结果是什么呢?答案是:true true false true,有初学java的朋友肯定会纳闷,a==c为什么会是false呢?equals判断的为什么都是true呢?

根据这些问题,我们就通过对String的解读来一步一步的了解。

为什么a==c的结果是false

明白这个问题需要对JVM的内存结构有一定的了解,说是了解也不需要太多,能够get到下图的知识点就行了。

ps:本文中所有的图示均是为了方便理解,画出来的大致样子,如果想要了解的更加清楚,请自行研究虚拟机原理。

java语法设计的时候针对String,提供了两种创建方式和一种特殊的存储机制(String intern pool )。

两种创建字符串对象的方式:

  1. 字面值的方式赋值
  2. new关键字新建一个字符串对象

这两种方法在性能和内存占用方面存在这差异

String Pool串池:是在内存堆中专门划分一块空间,用来保存所有String对象数据,当构造一个新字符串String对象时(通过字面量赋值的方法),Java编译机制会优先在这个池子里查找是否已经存在能满足需要的String对象,如果有的话就直接返回该对象的地址引用(没有的话就正常的构造一个新对象,丢进去存起来),这样下次再使用同一个String的时候,就可以直接从串池中取,不需要再次创建对象,也就避免了很多不必要的空间开销。

根据以上的概念,我们再来看前言中的代码,当JVM执行到String a = "abc";的时候,会先看常量池里有没有字符串刚好是“abc”这个对象,如果没有,在常量池里创建初始化该对象,并把引用指向它,如下图。

当执行到String b = "abc";时,发现常量池已经有了abc这个值,于是不再在常量池中创建这个对象,而是把引用直接指向了该对象,如下图:

继续执行到 String c = new String("abc");这时候我们加了一个new关键字,这个关键字呢就是告诉JVM,你直接在堆内存里给我开辟一块新的内存,如下图所示:

这时候我们执行四个打印语句,我们需要知道==比较的是地址,equals比较的是内容(String中的重写过了),abc三个变量的内容完全一样,因此equals的结果都是true,ab是一个同一个对象,因此地址一样,a和c很显然不是同一个对象,那么此时为false也是很好理解的。

String相关源码

在本文中只有String的部分源码,毕竟String的源码有3000多行,全部来写进来不那么现实,我们挑一些比较有意思的代码来做一定的分析说明。

属性

我们先来看一下String都有哪些成员变量,比较关键的属性有两个,如下:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
char数组
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0

从源码中我们能够看到,在String类中声明了一个char[]数组,变量名value,声明了一个int类型的变量hash(该String对象的哈希值的缓存)。也就是说java中的String类其实就是对char数组的封装。

构造方法

接下来我们通过一句代码来了解一下字符串创建的过程,String c = new String("abc");我们知道使用new关键字就会使用到构造方法,所以如下。

public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

构造方法中的代码非常简单,把传进来的字符串的value值,也就是char数组赋值给当前对象,hash同样处理,那么问题来了WTF original?

在这里需要注意的是java中的一个机制,在Java中,当值被双引号引起来(如本示例中的"abc"),JVM会去先检查看一看常量池里有没有abc这个对象,如果没有,把abc初始化为对象放入常量池,如果有,直接返回常量池内容。所以也就是说在没有“abc”的基础上,执行代码会在串池中创建一个abc,也会在堆内存中再new出来一个。最终的结果如下图:

那么这时候如果再有一个String c2 = new String("abc");呢?如图

关于这一点我们通过IDEA的debug功能也能够看到,你会发现,c和c2其中的char数组的地址是相同的。足以说明在创建c和c2的时候使用的是同一个数组。

equals方法

public boolean equals(Object anObject) {
//如果两个对象是同一个引用,那么直接返回true
if (this == anObject) {
return true;
}
/*
1.判断传入的对象是不是String类型
2.判断两个对象的char数组长度是否一致
3.循环判断char数组中的每一个值是否相等
以上条件均满足才会返回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;
}

为什么String不可变?

串池需要

为什么说是串池需要呢?在开篇的时候我们提到过,串池中的字符串会被多个变量引用,这样的机制让字符串对象得到了复用,避免了很多不必要的内存消耗。

那么大家试想一下,如果String对象本身允许二次修改的话,我有一个字符串“abc”同时被100个变量引用,其中一个引用修改了String对象,那么将会影响到其他99个引用该对象的变量,这样会对其他变量造成不可控的影响。

不可变性的优点

安全性

字符串不可变安全性的考虑处于两个方面,数据安全和线程安全。

数据安全,大家可以回忆一下,我们都在哪些地方大量的使用了字符串?网络数据传输,文件IO等,也就是说当我们在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。

线程安全,因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享,这样便不用因为线程安全问题而使用同步。

性能效率

关于性能效率一方面是复用,另一方面呢需要从hash值的缓存方向来说起了。

String的Hash值在很多的地方都会被使用到,如果保证了String的不可变性,也就能够保证Hash值始终也是不可变的,这样就不需要在每次使用的时候重新计算hash值了。

String不可变性是如何实现的?

通过对属性私有化,final修饰,同时没有提供公开的get set方法以及其他的能够修改属性的方法,保证了在创建之后不会被从外部修改。

同时不能忘了,String也是被final修饰的,在之前的文章中我们提到过,final修饰类的结果是String类没有子类。

那么String真的不能改变吗?不是,通过反射我们可以,代码如下:

String c = new String("abc");
System.out.println(c);
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value"); //改变value属性的访问权限
valueFieldOfString.setAccessible(true); //获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(c); //改变value所引用的数组中的第5个字符
value[1] = '_';
System.out.println(c);
执行的结果是 abc
a_c

也就是说我们改变了字符串对象的值,有什么意义呢?没什么意义,我们从来不会这么做。

其他问题

不是特别需要请不要使用new关键字创建字符串

从前文我们知道使用new关键字创建String的时候,即便串池中存在相同String,仍然会再次在堆内存中创建对象,会浪费内存,另一方面对象的创建相较于从串池中取效率也更低下。

String StringBuffer StringBuilder的区别

关于三者的区别,在面试题中经常的出现,String对象不可变,因此在进行任何内容上的修改时都会创建新的字符串对象,一旦修改操作太多就会造成大量的资源浪费。

StringBuffer和StringBuilder在进行字符串拼接的时候不会创建新的对象,而是在原对象上修改,不同之处在于StringBuffer线程安全,StringBuilder线程不安全。所以在进行字符串拼接的时候推荐使用StringBuffer或者StringBuilder。

  

  

  

  

String全面解析的更多相关文章

  1. IEnumerable<IEnumerable<string>>结构解析通用解决方案(支持指定属性顺序)

    一.前言 类似如下字符串 "ID", "NameValue", "CodeValue", "ExchangeTypeValue&q ...

  2. String深度解析

    文章出处:http://my.oschina.net/xiaohui249/blog/170013 一.引题 String类型是比较特殊的一种类型,同时也是面试经常被问到的一个知识点,本文结合java ...

  3. JDK源码之String类解析

    一 概述 String由final修饰,是不可变类,即String对象也是不可变对象.这意味着当修改一个String对象的内容时,JVM不会改变原来的对象,而是生成一个新的String对象 主要考虑以 ...

  4. Java之String重点解析

    String s = new String("abc")这段代码创建了几个对象呢?s=="abc"这个判断的结果是什么?s.substring(0,2).int ...

  5. String的解析

    String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处. 1 ...

  6. Java基础(三) String深度解析

    String可以说是Java中使用最多最频繁.最特殊的类,因为同时也是字面常量,而字面常量包括基本类型.String类型.空类型. 一. String的使用 1. String的不可变性 /** * ...

  7. Java String 类解析

    I.构造函数: public String() {} 默认构造函数 public String(String original) {} 使用原有字符串构造  public String(char va ...

  8. java中的String要点解析

    String类使我们经常使用的一个类,经常用来表示字符串常量. 字符串一旦被创建赋值,就不能被改变,因为String 底层是数组实现的,且被定义成final类型.我们可以看String源码. /** ...

  9. String相加解析

    Java代码 package com.utils.test; public class TestObject { public static void main(String[] args) { St ...

随机推荐

  1. HTML里面form表单name,action,method,target,enctype等属性用法

    HTML里面的form表单里面的name,target,enctype,method以及action的用法 HML表单HTML里面的表单是HTML页面与浏览器交互的重要手段,表单主要提交一些客户端的数 ...

  2. Java线程池(ThreadPoolExecutor)原理分析与使用

    在我们的开发中"池"的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 使用线程池的好处 1.降低资源消耗 可以重复利用 ...

  3. Python——破解极验滑动验证码

    极验滑动验证码 以上图片是最典型的要属于极验滑动认证了,极验官网:http://www.geetest.com/. 现在极验验证码已经更新到了 3.0 版本,截至 2017 年 7 月全球已有十六万家 ...

  4. 一个 react 小的 demo

    一.搭建开发环境: webpack构建工具. 新建一个文件夹(login),进入根目录, 1.输入命令:cnpm init,生成了一个package.json文件,这是一个标准的npm说明文件,里面蕴 ...

  5. chrome如何添加扩展程序及登录

    https://jingyan.baidu.com/album/7e440953191a2b2fc0e2ef0c.html?picindex=3

  6. 如何把开源项目发布到Jcenter

    转载自:https://www.jianshu.com/p/f66972f0607a 首先我们应该注册一个JFrog Bintray的账号 Jfrog Bintray官网 这里我们可以注意到那个绿色的 ...

  7. 【Java基本功】一文了解Java中继承、封装、多态的细节

    本节主要介绍Java面向对象三大特性:继承 封装 多态,以及其中的原理. 本文会结合虚拟机对引用和对象的不同处理来介绍三大特性的原理. 继承 Java中的继承只能单继承,但是可以通过内部类继承其他类来 ...

  8. Mysql 用户权限管理

    1. MySQL 权限介绍 mysql中存在4个控制权限的表,分别为user表,db表,tables_priv表,columns_priv表,我当前的版本mysql 5.7.22 . mysql权限表 ...

  9. 3分钟看完Java 8——史上最强Java 8新特性总结之第三篇 函数式编程技巧

    目录 · 改写设计模式 · 策略模式(Strategy Pattern) · 模板方法模式(Template Method Pattern) · 观察者模式(Observer Pattern) · 责 ...

  10. HTML5 audio元素如何使用js与jquery控制其事件

    前言: 每一次遇见问题想到的就是怎么解决?最好的方法还是查询网络媒体,更好的办法是让自己记忆,只有自己理解到了才真正是属于自己.要做一个订单提醒功能,没有使用audio相关的插件,虽然插件无数,还是喜 ...