前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >String没那么简单!

String没那么简单!

作者头像
Java技术江湖
发布2019-09-24 13:48:52
5930
发布2019-09-24 13:48:52
举报
文章被收录于专栏:微信公众号【Java技术江湖】

String类相信大家都不陌生,我们写工程使用String的频率,就像写作文时使用汉字“的”的频率一样高。那么你经常使用它,是否真的“了解”它?请带着问题,一步一步的揭开它神秘的面纱,看看它到底是何许“人”也!

一、先简单了解jvm

大家都知道jvm也就是Java虚拟机,这里我们只说下JVM的内存区,运行时内存区主要可以划分为:栈 (Java Virtual Machine Stacks) 、 堆内存 (Heap Memory)、方法区 (Method Area) 、本地方法栈 (Native Method Stacks) 、和程序计数器 (Program Counter (PC) Register)。

1.栈

栈是线程私有,自动分配的连续的空间,后进先出。栈用来存放栈帧。栈帧用于存储 局部变量表、操作数栈、动态链接、方法返回等信息。 每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈内存中的数据没有初始化默认值,需要手动设置,且数据执行完毕,变量会立即释放。这样可以节约内存空间。

2.堆内存

堆内存是线程共享,且是不连续的空间。堆用来存放new创建的对象和数组,与栈不同,堆内存中的实体是用来封装数据的,都是有初始化默认值的。

3.方法区

方法区和堆一样都是线程共享。方法区又被称为静态区,它用于存储已被虚拟机加载的 类信息、常量、静态变量等数据。jdk1.8已经将方法区取消,替代的是元数据区,这是一块堆外的直接内存,与jdk1.6和jdk1.7方法区不同。jdk1.6和jdk1.7方法区理解为永久区(具体可以看下面两张图)。

4.常量池

运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用如声明为final的常量值等。并且池中的字符串的内容不重复!

java7内存结构图

java8内存结构图

二、了解String

String常量,它的值是在常量池中的。JDK8 已经将常量池转移到Java堆中而不是方法区。

想要从本质上了解String类,我建议可以先看看String类的源码。

String内部是用final修饰,并且可序列化,可比较,不可变,不能被继承,实现了Serializable,Comparable,CharSequence接口。同时String类是通过char数组来保存字符串的,char又被final修饰,所以说字符串是常量,它们的值在创建之后不能更改。

String类重写了hashCode方法,String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。同一个String对象 hashCode 一定相同, 但是 hashCode相同 ,不一定是同一个对象。

代码语言:javascript
复制
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

构造方法:通过字符数组,StringBuffer,StringBuilder进行初始化时,就要执行value数组元素的拷贝,创建新数组,防止外部对value内容的改变。

代码语言:javascript
复制
   public String() {
        this.value = "".value;
    }
    
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
//参数为char数组,使用java.utils包中的Arrays类复制
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
//略
// 检查越界
    private static void checkBounds(byte[] bytes, int offset, int length) {
        if (length < 0)
            throw new StringIndexOutOfBoundsException(length);
        if (offset < 0)
            throw new StringIndexOutOfBoundsException(offset);
        if (offset > bytes.length - length)
            throw new StringIndexOutOfBoundsException(offset + length);
    }
//从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
    }
    
    public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBounds(bytes, offset, length);
        this.value =  StringCoding.decode(charset, bytes, offset, length);
    }
//调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

    public String(byte bytes[], Charset charset) {
        this(bytes, 0, bytes.length, charset);
    }
    
    public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
    }

    public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

    public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    
    public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
//内部构造方法 , 能修改value,外部无法访问
    String(char[] value, boolean share) {
        this.value = value;
    }

当使用任何方式来创建一个字符串对象时,运行中JVM会拿着这个对象在String池中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串,否则,不在池中添加。

3.String类的intern()方法

上面英文注释翻译过来就是:在调用intern方法时,如果池已经包含了string等于这个string的对象(由equals(Object)方法判断),则池中的字符串返回。否则,将此String对象添加到池中和返回这个String对象的引用。

三、加深印象

如果上面只是了解了概念,那下面就来做几道题,加深印象吧!

1.下面一共创建了几个对象?

代码语言:javascript
复制
 String str1 = "bbc";
 String str2 = "bbc";

答案:一个对象(常量池对象),JVM先查找常量池中存不存在"bbc"这个对象,如果不存在,就在常量池中创建对象"bbc",然后把池中"bbc"对象的引用地址返给str1,这时str1指向池中"bbc"字符串对象。如果存在,则不创建任何对象,直接将常量池中的"bbc"对象的地址返回给str2。

2.再问,下面一共创建了几个对象?

代码语言:javascript
复制
 String str1 = new String("bbc");
 String str2 = new String("bbc");

答案:一共是三个对象(new创建的两个对象和一个常量池中创建的对象),String str1 = new String("bbc");这种方式创建字符串实际生成了两个字符串对象。首先,构造器中传入了一个字符串对象 ,“bbc”它就被放在字符串常量池中。创建str2时由于常量池中已经存在"bbc",所以不会重复创建。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java技术江湖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档