java基础--字符串常量池(String Table)

概述

  1. 常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式;当然也可扩充,执行器执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间
  2. 因为在Java中创建一个对象是一个很重的活,并且需要不断进行垃圾回收,所以像是String Table这样的缓冲池可以有效缓解这些问题。(很多包装类都有缓冲空间,Integer 默认缓存 -128 ~ 127 区间的值,Long 和 Short 也是缓存了这个区间的值,Byte 只能表示 -127 ~ 128 范围的值,全部缓存了,Character 缓存了 0 ~ 127 的值。Float 和 Double 没有缓存的意义,因为这两种类型表示小数,可能性倍增,所以不适合应用缓存池的概念)
  3. 字符串常量池String Table的数据结构是一个哈希表,但是这个哈希表与Java集合中的哈希表不同,无法进行扩容操作,并且字符串种类复杂,很可能发生哈希碰撞现象,一旦字符串在哈希表中形成了链表等数据结构,就会使字符串常量池的性能下降,所以字符串常量池中需要加入垃圾回收机制。

字符串常量池在JVM中的位置变化:

jdk6及之前在方法区中,但是在jdk6中已经有向对堆中迁移的趋势。
jdk7是JVM内存区域发生了变化,将方法区放到了直接内存中,而字符串常量池放到了堆空间当中。 

 字符串常量池

  1. 在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。
  2. 常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种。
    1. 直接使用双引号声明出来的String对象会直接存储在常量池中。比如:String info="atguigu.com";
    2. 如果不是用双引号声明的String对象,可以使用String提供的intern()方法。 
  3. 所有的字符串都保存在(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。
     

String基本操作中的细节

示例一

public class StringTest {
    public static void main(String[] args) {
            System.out.println(); 
            System.out.println("1"); 
            System.out.println("2"); 
            //如下的字符串"1" 到 "2"不会再次加载
            System.out.println("1"); 
            System.out.println("2");  
	}

示例二

public class Test1 {
    public static void main(String[] args) {
        String str1="hello";
        String str2="hello";
        String str3=new String("world");
        String str4=new String("world");
        System.out.println(str1==str2);
        System.out.println(str1==str3);
        System.out.println(str3==str4);
    }
}
//true
//false
//false

思考:为什么以字符常量串创建的时候,创建的字符串的地址一样,而用new String的时候结果的地址是不一样的呢?也就是为什么str1和str2引用的对象是一样的,而str3和str4不一样。

为了使程序的运行速度更快、更节省内存,Java为8种基本数据类型和String类都提供了常量池。

为了节省存储空间以及程序的运行效率,Java中引入了:

  1. class文件常量池:每个.Java源文件编译后生成.class文件中会保存当前类中的字面常量以及符号信息
  2. 运行时常量池:在.class文件被加载时,.class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份
  3. 字符串常量池

 字符串常量池(StringTable)

        字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(一种高效用来进行查找的数据结构)不同JDK版本下字符串常量池的位置以及默认大小是不同的 

JDK版本 字符串常量池位置 大小设置
Java6 (方法区)永久代 固定大小:1009
Java7 堆中 可设置,没有大小限制,默认大小:60013
Java8 堆中 可设置,有范围限制,最小是1009

再谈String对象创建

当直接用字符串常量进行赋值的时候: 

public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "hello";
    System.out.println(s1 == s2); // true
}

java基础--字符串常量池(String Table)_第1张图片

        当字节码文件加载的时候字符串hello已经创建好了并且保存在字符串常量池中,当使用常量串赋值的时候优先从字符串常量池找,找到了就将该字符串引用赋值给要赋值的对象。 所以会出现这两个字符串地址一致的情况。

当我们用new来创建String对象的时候,由于new的对象是唯一的,所以地址都是不一样的

使用常量串创建String类型对象的效率更高,而且更节省空间。用户也可以将创建的字符串对象通过intern方式添加进字符串常量池中。

intern方法
intern是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。 

public static void main(String[] args) {
char[] ch = new char[]{'a', 'b', 'c'};
String s1 = new String(ch); // s1对象并不在常量池中
//s1.intern(); // s1.intern();调用之后,会将s1对象的引用放入到常量池中
String s2 = "abc"; // "abc" 在常量池中存在了,s2创建时直接用常量池中"abc"的引用
System.out.println(s1 == s2);
} /
/ 输出false
// 将上述方法打开之后,就会输出true

字符串的不可变性

String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:

1. String类在设计时就是不可改变的,String类实现描述中已经说明了

java基础--字符串常量池(String Table)_第2张图片

String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:

  1. String类被final修饰,表明该类不能被继承
  2. value被修饰被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改
  3.  所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象比如 replace 方法: 

java基础--字符串常量池(String Table)_第3张图片

网上有人说:字符串不可变是因为其内部保存字符的数组被final修饰了,因此不能改变。这种说法是错误的,不是因为String类自身,或者其内部value被final修饰而不能被修改。

final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。

如果我们想修改字符串建议尽量使用StringBuffer或者StringBuilder。

注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下。 

你可能感兴趣的:(初学java,java,jvm,数据结构)