StringTable(尚硅谷自学笔记)

String的基本特性

String :字符串,使用一对""引起来表示。

String声明为final的,不可被继承

String实现了serializable接口:表示字符串是支持序列化的,实现了Comparable接口:表示string可以比较大小

String在jdk8及以前内部定义了final char[ ] value用于存储字符串数据。jdk9时改为byte []

为啥要改呢?

来自Oracle官网

The current implementation of the String class stores characters in a chararray, using two bytes (sixteen bits) for each character. Data gathered frommany different applications indicates that strings are a major component ofheap usage and, moreover, that most String objects contain only Latin-1 characters. 字符串类的当前实现将字符存储在字符数组中,每个字符使用两个字节(16位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且大多数字符串对象只包含拉丁文。

Such characters require only one byte of storage, hence half of thespace in the internal char arrays

of such String objects is going unused**.这样的字符只需要一个字节的存储**,因此这类字符串对象的内部char数组中的空间有一半是未使用的。

We propose to change the internal representation of the String class fromUTF-16 char array to a byte array plus an encoding-flag field.The new Stringclass will store characters encoded either as ISO-8859-1/Latin-1 (one byte percharacter), or as UTF-16 (two bytes per character), based upon the contentsof the string. The encoding flag will indicate which encoding is used. 我们建议将字符串类的内部表示形式从UTF-16字符数组更改为字节数组加编码标志字段,新的Stringclass将根据字符串的内容存储编码为ISO-8859-1/拉丁文-1(每个字符一个字节)或UTF-16(每个字符两个字节)的字符。编码标志将指示所使用的编码。

String:代表不可变的字符序列。简称:不可变性。

>当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。

>当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

>当调用String的replace ()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

代码1:

public class stringTest1 {
    @Test
    public void test1() {
        String s1 = "abc";
        string s2 = "abc";
        //s1 = "hello" ;
        System.out.println(s1 == s2);//true 都指向常量池的同一个地址
        System.out.println(s1);//abc
        System.out.println(s2);//abc
    }
    
    @Test
    public void test2() {
    	String s1 = "abc";
        String s2 = "abc";
        s2+="def";
    	System.out.println(s2);//abcdef
        System.out.println(s1);//abc
        //可以看出这对s1并没有啥影响
    }

    @Test
    public void test3() {
        String s1 = "abc";
        String s2 = s1.replace(oldChar: 'a',newChar: 'm' );system.out.println(s1);//
        System.out.println(s2);//
    }

}

代码2:

public class StringExer {

    String str = new string( original: "good");
    char[] ch = { 't', 'e', 's', 't'};
    public void change(string str, char ch[]) {
        str = "test ok";
        ch[0] = 'b ';
    }


    public static void main(String[] args) {
    	StringExer ex = new StringExer();
        ex.change(ex.st,ex.ch);
    	System.out.println(ex.str);//good java都是值传递,修改了内容只是修改了副本
        System.out.println(ex.ch);//best
    }
}

字符串常量池中是不会存储相同内容的字符串的。

String的String Pool(常量池)是一个固定大小的Hashtable(数组加链表),默认值大小长度是1009。如果放进string Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。

使用-XX :StringTableSize可设置stringTable的长度

在jdk6中stringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize设置没有要求

在jdk7中,StringTable的长度默认值是60013

在jdk8中改为1009是可设置的最小值。

String的内存分配

在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念

常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种

直接使用双引号声明出来的String对象会直接存储在常量池中。比如:String info = “atguigu.com” ;

如果不是用双引号声明的String对象,可以使用String提供的intern()方法。

Java 6及以前,字符串常量池存放在永久代。

Java 7 中oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内。

所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。

字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7 中使用string.intern ( ) 。

Java8元空间,字符串常量在堆。

String的拼接

1.常量与常量的拼接结果在常量池,原理是编译期优化

2.常量池中不会存在相司内容的常量。

3.只要其中有一个是变量,结果就在堆中。变量拼接的原理是stringBuilder

4.如果拼接的结果调用intern ()方法,则主动将常量池中还没有的字符串对象入池中,并返回此对象地址。

public class Test{
    @Test
    public void test1(){
        String s1 = "a" + "b" + "c";//
        String s2 = "abc ";
        /*
        * 最终.java编译成.cLass,再执行.cLass
        * string s1 = "abc";
        * string s2 = "abc"
        */
        System.out.println(s1 == s2);//true 都指向常量池
        System.out.println(s1.equalE(s2)); //true
    }
    
    public void test2(){
        String s1 = "javaEE";
        String s2 = "hadoop" ;
        String s3 = "javaEEhadoop";
        String s4 = "javaEE" + "hadoop" ;//编译期优化
        //如果拼接的前后出现了变量,则相当于在堆空间new String(),具体内容为拼接的结果
        String s5 = s1 + "hadoop" ;
        String s6 = "javaEE" +s2;
        String s7 = s1 + s2;
        System.out.println(s3 ==s4);//true
        System.out.println(s3 == s5);//false
        System.out.println(s3 -= s6);//false
        System.out.println(s3 == s7);//false
        System.out.println(s5 == s6);//false
        System.out.println(s5 ==s7);//false
        System.out.println(s6==s7);//false
        //判断常量池中是否存在与s6相等的值,如果存在则返回常量池中s6常量值的位置
        //若不存在,则在string常量池中加载一份与s6值相同的,并返回地址
        String s8 = s6.intern();
        System.out.println(s3 ==s8);//true
    }
}

上面为什么会出现false呢,底层是什么?

先举一个代码例子

public class Test{
	@Test
    public void test(){
    	String a = "a";
        String b = "b";
        String c = a + b;
        String d = "ab";
        System.out.println(d == c);
    }
}

如下的s1 + s2的执行细节:

①StringBuilder s = new StringBuilder();

②s.append(“a”)

③s.append(“b”)

④s.tostring()–>类似于new string( “ab”)

public class Test{
	@Test
    public void test4(){
        final String s1 = "a;
        final String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);//true
    }
}

s1加上final后已经被认为是常量了,就不会调用上面的StringBuilder在堆空间new一个String,仍然使用编译期优化,直接在常量池找

拼接操作和appen操作的对比

img

method1用了4000,method2只用了7

可知效率差距之大,其主要原因是因为在method1中,每经过一次循环都会创建一个StringBuilder调用append,然后再toString出来,效率自然很低下

intern方法

如果不是用双引号声明的string对象,可以使用string提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。

·比如:string myInfo = new string (“I love atguigu”).Intern() ;

也就是说,如果在任意字符串上调用string.intern方法,那么其返回结果所指向的那个类实例,必须和直接以常量形式出现的字符串实例完全相同。因此,下列表达式的值必定是true:

( “a” + “b” + “c” ) .intern ( ) == “abc”

通俗点讲,Interned string就是确保字符串在内存里只有一份拷贝,这样可以节约内存空间,加快字符串操作任务的执行速度。注意,这个值会被存放在字符串内部池

面试题

揭开下面的代码的答案之前先来拥有一些基本的知识储备

img

题目1:

会有两个对象,首先是new出来的String,第二个是String构造器中的"ab",会在常量池中创建一个相同的对象ab。

扩展:

new string( “a”) + new string( “b”)呢?

对象1: new stringBuilder()

对象2:new string(“a”)

对象3:常量池中的"a"

对象4:new string( “b”)

对象5:常量池中的"b"

深入剖析:

stringBuiLder的tostring() :

对象6:new String(“ab”);

强调一下toString的调用,在常量池是不会有ab的

public class StringIntern1 {
public static void main(String[ ] args) {
    String s = new String( "1");
    s.intern();//调用此方法之前字符串常量池已经有1了
    String s2 = "1";
    System.out.println(s == s2);//jdk6 : false jdk7 : false
    String s3 = new String("1") + new String("1");
    s3.intern();//在字符串常量区中生成11
    String s4 = "11";
    System.out.println(s3 == s4);//jdk6 : false jdk7 : true
}
	

对于第一个情况,即s == s2 //false的情况,在String s = new String(“1”);这行代码常量池已经存在一个1的对象了,调用s.intern()并没有什么用,因为1的对象地址仍然是常量池中自己的地址

但是对于第二个情况呢, String s3 = new String(“1”) + new String(“1”);此时就算用StringBuilder中的append然后再是toString的自动newString(“11”),常量池中始终不存在11,所以调用s3的intern方法,常量池中的地址就会自动引用s3的地址,让11和s3的地址一样了

img

intern的效率测试
public class StringIntern2 {
    static final int MAX_COUNT = 1000*10000;
    static final String[] arr = new String[MAX_COUNT];
    public static void main(String[] args) {
        Integer[] data = new Integer[]{1,2,3,4,5,6,7,8,9,10};long start = System.currentTimeMiLlis();
        for (int i = 0; i <MAX_COUNT; i++){
            arr[i] = new String(String. value0f ( data[i % data.Length]));
            arr[i] = new String(String.valueof(data[i % data.length] ) ).intern();
    	}
    long end = System.currentTimeMiLlis();
    System.out.println("花费的时间为:" +(end - start));
        try {
        Thread.sleep(1000000);
        } catch (InterruptedException e){
            e.printStackTrace();
            System.gc();
        }
    }
}

当用了intern方法之后,内存占用明显减少了。对于字符串大量存在的重复字符串,使用intern方法可以节省内存空间

G1的String去重操作

背景:对许多Java应用(有大的也有小的)做的测试得出以下结果:

>堆存活数据集合里面string对象占了25%

≥堆存活数据集合里面重复的string对象有13.5%

>string对象的平均长度是45

许多大规模的Java应用的瓶颈在于内存,测试表明,在这些类型的应用,Java堆中存活的数据集合差不多25%是string对象。更进一步,这里面差不多一半string对象是重复的,重复的意思是说:stringl.equals (string2) =true。堆上存在重复的string对象必然是一种内存的浪费。这个项目将在G1垃圾收集器中实现自动持续对重复的string对象进行去重,这样就能避免浪费内存。

img

img

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值