String类解析JDK1.8

字符串的创建方式

 		//1.方法一 字面量的方式 
        String str1 = "123456";

        //2.方法二
        String str2 = new String("zhw");
1.通过字面的方式创建的字符串存放在常量池中
2.通过new的方式创建的字符串存放在堆中

String的概述

特点

1.String类由final关键字修饰,不可以被继承,并且一旦创建不可更改
2.jdk8后String中的数据由Byte数组存储,并且不可以扩容
	public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
    //用于存储数据
    private final byte[] value;
    //用于指定存放的数据的使用哪一种编码方式,一旦指定就不可变
	private final byte coder;
    ......
    }

为什么改为byte来存储数据

  1. 因为char在内存中需要占据2个字节,而在程序中存储的大部分是拉丁文,用不了2个字节,用Byte类型也足够用了。
  2. 那么怎么存储中文哪?中文需要2字节存储。在String类中新增了一个coder常量用来标记使用什么编码方式来存储数据。
public String(byte bytes[], int offset, int length, Charset charset) {
        if (charset == null)
            throw new NullPointerException("charset");
        checkBoundsOffCount(offset, length, bytes.length);
        StringCoding.Result ret =
            StringCoding.decode(charset, bytes, offset, length);
        this.value = ret.value;
        this.coder = ret.coder;
    }

String的不可变性

//测试String的不可变性
public class StringDemo3 {
    public static void main(String[] args) {
        //s1和s2同时指向字符串常量池的地址
        String s1 = "abc";
        String s2 = "abc";

        //s3指向字符串常量池中的该值的地址
        String s3 = "abcdef";

        
        /**
         * 先创建一个StringBuilder对象将s1的值和"def"用append方法加入最后用ToString返回
         * StringBuilder ToString()方法返回的数据存放在堆中还是字符串常量池中?
         * 结果存放到了堆中
         */
        //s1指向堆中的地址
        s1 = s1 + "def"; //abceefg
        
        System.out.println(s1 == s3);//false
    }
}

对应的字节码
在这里插入图片描述


    public void test2() {
        //s1和s2同时指向字符串常量池的地址
        String s1 = "abc";
        //在s1的基础上将形成一个新的值但是不改变原来的s1,且该值在堆内存中
        String s2 = s1.replace('a', 'd');
        String s3 = "dbc";
        System.out.println(s2 == s3); //false
    }

对应的字节码
在这里插入图片描述

@Test
    public void test3(){
        String str1 = new String("zzz");


        char arr[] = {'b','c','d'};

        System.out.println("没有change的地址");
        System.out.println("str1的地址:"+str1.hashCode()); //str1的地址:121146
        System.out.println("arr的地址:"+arr.hashCode()); //arr的地址:627185331



        change(str1,arr);

        System.out.println("=================");
        System.out.println("change后的地址str的地址:"+str1.hashCode()); //change后的地址str的地址:121146
        System.out.println("change后的地址arr的地址:"+arr.hashCode()); //change后的地址arr的地址:627185331

        System.out.println(str1);
        System.out.println(arr);

    }

    public void change (String str,char arr[]){
        //可以看到是又有一个地址指向 zzz的地址
        System.out.println(str); //zzz
        str = "hello";
        arr[0] = 'a';
        System.out.println("=========================");
        System.out.println("change方法里str的地址:"+str.hashCode()); //change方法里str的地址:99162322
        System.out.println("change方法里arr的地址:"+arr.hashCode()); //change方法里arr的地址:627185331

    }

在这里插入图片描述
在这里插入图片描述

String的基本特性

1.字符串常量池中是不会存储相同的字符串的

在这里插入图片描述

设置运行时的参数

在这里插入图片描述

在这里插入图片描述

String内存结构的分布位置

1.通过字面量的方式创建的字符串会保存在字符串常量池中,并且只保存一份,当有相同的数据被创建时,不会再添加
在这里插入图片描述

public class Memory {
    public static void main(String[] args) {
        Object o = new Object(); //存放到堆空间中
        Memory memory = new Memory(); //存放到堆空间中
        memory.foo(o); //开启一个新的栈帧
    }

    public void foo(Object param){
        String s = param.toString(); //将param生成的数据存放到字符串常量池中
        System.out.println(s);
    }
}

在这里插入图片描述

字符串的拼接操作

在这里插入图片描述

/**
 * 字符串的拼接操作
 */
public class StringDemo5 {

    @Test
    public void test1() {
        //在编译期间就已经优化了
        String s1 = "a" + "b";
        String s2 = "ab";

        System.out.println(s1 == s2); //true
        System.out.println(s1.equals(s2)); //true
    }

    @Test
    public void test2() {
        //在编译期间就已经优化了
        String s1 = "a";
        String s2 = "b";

        String s3 = "ab";
        String s4 = "a" + "b"; // 编译期间优化
        String s5 = s1 + "b"; //使用StringBuilder进行操作
        String s6 = "a" + s2; //使用StringBuilder进行操作
        String s7 = s1 + s2; //使用StringBuilder进行操作

        System.out.println(s3 == s4); //true
        System.out.println(s3 == s5); //false
        System.out.println(s3 == s6); //false
        System.out.println(s3 == s7); //fasle
        System.out.println(s5 == s6); //false
        System.out.println(s5 == s7); //false
        System.out.println(s6 == s7); //false

        String s8 = s6.intern(); //会从字符串常量池中获取“ab”值的地址
        //因为s3本身就在字符串常量池中,s8又是从字符串常量池中获取的
        System.out.println(s3 == s8); //true
    }

    @Test
    public void test3() {
        //在编译期间就已经优化了
        String s1 = "ab";
        String s2 = "a";
        String s3 = s2 + "b";
        System.out.println(s1 == s3); //false

        final String s4 = "a";
        String s5 = s4 + "b";
        System.out.println(s1 == s5); //true ? 因为s4由final关键字修饰此时s4为常量
        
    }
}

StringBuilder的append方法和String的拼接的执行效率

package com.zhw.String;


import org.junit.Test;

/**
 * append操作和字符串的拼接的执行效率
 */
public class StringDemo6 {
    public static   void main(String[] args) {
        StringDemo6 stringDemo6 = new StringDemo6();
        long begin = System.currentTimeMillis();

        stringDemo6.join(100000);//用时2375
        long time1 = System.currentTimeMillis() - begin;
        System.out.println(time1);


        long begin1 = System.currentTimeMillis();

        stringDemo6.append(100000); //用时8
        long time2 = System.currentTimeMillis() - begin1;
        System.out.println(time2);

        System.out.println("相差"+time1/time2+"倍"); //相差214倍
    }

    public void append(int times){
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < times; i++) {
            stringBuilder.append("a");
        }
//        System.out.println(stringBuilder.toString());
    }

    public void join(int times){
        String s = "";
        for (int i = 0; i < times; i++) {
           s += "a"; //每次都会创建StringBuilder和String对象
        }
//        System.out.println(s);
    }

}

intern()方法的理解

	When the intern method is invoked, if the pool already contains a
    string equal to this {@code String} object as determined by
    the {@link #equals(Object)} method, then the string from the pool is
    returned. Otherwise, this {@code String} object is added to the
    pool and a reference to this {@code String} object is returned.
    
    It follows that for any two strings {@code s} and {@code t},
    {@code s.intern() == t.intern()} is {@code true}
    if and only if {@code s.equals(t)} is {@code true}.  
    if and only if   ====>     <==>等价于

底层会调用equals()方法进行对比,如果池中已经有该对象,则返回池中的字符串的引用。 否则,这个对象将被添加到池中,并返回这个对象的引用。
在这里插入图片描述

package com.zhw.String;


/**
 * append操作和字符串的拼接的执行效率
 */
public class StringDemo7 {
    public static void main(String[] args)  {
        //会创建至少一个对象,如果ab在字符串常量值中已经存在,就不会再在常量池中创建
        //但是一定会在堆中创建一个值为ab的String对象
        String s1 = new String("ab");
        String s2 = s1;
        System.out.println(s2 == s1); //true

        String s3 = new String("zhw").intern();
        String s4 = "zhw";
        System.out.println(s3 == s4); //true

        String s5 = new String("zzz");
        String s6 = "zzz";
        System.out.println(s5 == s6); //false

    }
}

new String()到底创建了几个对象?

/**
 * new String到底创建了几个对象
 */
public class StringDemo8 {
    public static void main(String[] args)  {
        String s1 = new String("ab");
        //看字节码知道是2个
        //一个对象是:new 关键字在堆空间中创建的
        //另一个是:字符串常量池中的
		 /**
         * 1.StringBuffer对象
         * 2.new String对象
         * 3.a在字符串常量池中的对象
         * 4.new String对象
         * 5.b在字符串常量池中的对象
         * 6.StringBuilder的toString方法也会创建一个新的对象,但是不会将添加到字符串常量池中
         */
        String s2 = new String("a") + new String("b"); // 等同于 new String("ab"),但是字符串常量池中并没有该数据
        s2.intern();//将ab添加到字符串常量池中
        String s3 = "ab";
        System.out.println(s2 == s3); //JDK14:false 
    }
}

在这里插入图片描述

在这里插入图片描述

intern的练习题

/**
 * intern练习题
 */
public class StringDemo9 {
    public static void main(String[] args) {
        String s = new String("1");
        s.intern();
        String s2 = "1";
        System.out.println(s == s2); //false

        String s3 = new String("1")+new String("1");
        //执行完上一行代码之后,字符串常量池中是否存在“11”呢?答案:不存在
        s3.intern();        //如何理解:jdk6会实打实的创建一个新的对象"11",在字符串常量池中
                            //jdk7不再常量池中创建"11"对象,而是指向堆空间中的"11"对象的地址

        String s4 = "11";    //s4指向的是堆空间中"11"的地址
        System.out.println(s3 == s4); //true

    }
}

intern的总结:
在这里插入图片描述
jdk1.7起,如果堆中有字符对象,常量池中没有,使用intern方法,会在常量池中保存堆空间中该对象的引用地址
要点:
StringBuilder的toString方法并不会把该字符加入到字符串常量池中

/**
 * intern练习题jdk1.8
 */
public class StringIntern2 {
    public static void main(String[] args) {
        
        String s3 = new String("1")+new String("1");
        //执行完上一行代码之后,字符串常量池中是否存在“11”呢?答案:不存在
        String s4 = s3.intern();//在常量池中复制一份堆中的“11”的地址
        
        System.out.println(s4 == "11"); //true 此时常量11也指向堆中的地址
        System.out.println(s3 == s4);   //true
        
    }
}

intern的空间使用效率

package com.zhw.String;

/**
 * intern的空间使用效率
 */
public class StringIntern3 {
    static final int MAX_COUNT = 1000 * 10000;
    static final String[] arrs = new String[MAX_COUNT];

    public static void main(String[] args) {
        Integer[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        long start = System.currentTimeMillis();

        for (int i = 0; i < MAX_COUNT; i++) {
//            arrs[i] = new String(String.valueOf(data[i % data.length]));
            arrs[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();
        }

    }
}


在这里插入图片描述
在这里插入图片描述
结论:当需要用到大量的字符串对象时,使用intern方法可以大大节约内存空间的使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值