Java中String的特性(JDK1.6、1.7、1.8中的常量池,拼接,intern方法等)

6 篇文章 0 订阅
5 篇文章 0 订阅

一、String的基本特性

  • String类声明是final的,不可以被继承。

  • String实现了Serializable接口(序列号接口),Comparable接口(比较接口)

  • String在JDK8中是用final char[]存储字符串数据的,JDK9后改为final byte[]存储数据的。因为char类型占两个字节,很多时候的数据只需要一个字节就能存放的,因此会浪费掉一半的空间,之后改成byte[]类型,同时为了能够存放两个字节的数据(例如汉字),会将在使用前判断字符集。

    例子:

    public class StringExer {
        String str = "good";
    
        public static void change1(String str) {
            str = "test";
        }
    
        public static void change2(StringExer stringExer) {
            stringExer.str = "test";
        }
    
        public static void main(String[] args) {
            StringExer ex = new StringExer();
            StringExer.change1(ex.str);
            System.out.println(ex.str);//good
            StringExer.change2(ex);
            System.out.println(ex.str);//test
        }
    
    }
    

    这里不管是change1(),还是change2()方法都是引用类型的传递。

    change1()方法:由于是引用类型的传递,所以传递的时候内存会再复制出指向"good"字符串的句柄。这时候有两个指向"good"的句柄,在方法内中的str改变成"test",我们知道字符串是不可改变的,所以它实际上是在常量池中创建了"test"字符串,然后复制来的那份句柄指向了新的地址,也就是"test",然而外面的对象还是指向原来的字符串,也就是为什么还是打应good的原因。在这里插入图片描述

    change2()方法:通用ex实例的句柄,对str属性进行修改,所以str属性会发生改变在这里插入图片描述

    总结:change1()方法是对参数本身的修改,但是change2()却是对对象的数据的修改

二、字符串常量池

  • 字符串常量池使用一个固定大小的HashTable来实现的。
  • 在JDK6及之前长度默认为1009,如果字符串经常出现指针碰撞则会导致String类操作的效率变慢(尤其是String.intern()方法),所以在JDK7时默认被修改为60013,而在JDK8及后最短长度不得低于1009。
  • 可以通过"-XX:StringTableSize"参数来设置字符串常量池的大小。
  • 通过字面量定义的字符串存放在字符串常量池,通过new出来的字符串存放在堆空间。
  • 字符串常量池位置变化的原因:
    • 永久代的空间较小,容易出现OOM异常。
    • 永久代垃圾回收频率低,但字符串大多数生命都是短暂的。

三、字符串的拼接操作

  • 使用字面量创建字符串是在字符串常量池中,使用new关键字创建的对象是在先创建一个String对象然后在字符串常量池中寻找是否有该字符串常量,若没有则创建该对象,最后让这个String对象字符串常量。
  • 两个字面量字符串拼接会在编译期进行优化。
  • 含有变量的字符串拼接:
    • 如果在JDK 5之后是先创建StringBuilder对象,再通过append()方法进行拼接,最后通过toString()方法返回新的字符串对象(跟new String()一致),所以最后返回的对象是创建在堆中的。
    • 在JDK 5之前使用StringBuffer,其余一样。
    • 补充:StringBuilder没有syn关键字,StringBuffer内的方法有syn关键字,因此StringBuilder是线程不安全的,StringBuffer是线程安全的,速度上StringBuilder>StringBuffer
  • 使用intern()方法:若该字符串不存在常量池中,则在常量池中创建该字符串。最后返回常量池中的字符串。
  • 使用final修饰的对象进行拼接的时候会将其认为字面量
@Test
public void StringTest() {
    String s1 = "a";
    String s2 = "ab";
    String s3 = "a" + "b";
    String s4 = s1 + "b";
    String s5 = new String("ab");
    String s6 = s5.intern();

    final String s7 = "c";
    String s8 = "cd";
    String s9 = s7 + "d";

    //在字节码文件中的s3被优化成s3="ab"
    System.out.println(s2 == s3);//true
    //因为s1是变量,所以最后是通过toString方法返回,对象是创建在堆中的
    System.out.println(s3 == s4);//false
    System.out.println(s3 == s5);//false
    //s6是通过intern从字符串常量池中获取的
    System.out.println(s3 == s6);//true

    System.out.println(s8 == s9);//true
}

String、StringBuffer、StringBuilder的拼接操作

    @Test
    public void test6() {

        long start = System.currentTimeMillis();

//        method1(100000);//4014
//        method2(100000);//5
        method3(100000);//9
        
        long end = System.currentTimeMillis();

        System.out.println("花费的时间为:" + (end - start));
    }

    public void method1(int highLevel) {
        String src = "";
        for (int i = 0; i < highLevel; i++) {
            src = src + "a";
        }
    }

    public void method2(int highLevel) {
        StringBuilder src = new StringBuilder();
        for (int i = 0; i < highLevel; i++) {
            src.append("a");
        }
    }

    public void method3(int highLevel) {
        StringBuffer src = new StringBuffer();
        for (int i = 0; i < highLevel; i++) {
            src.append("a");
        }
    }

String的拼接:创建一个StringBuilder,然后再根据append进行追加,再创建String返回。

StringBuilder的拼接:直接进行append拼接,没有syn关键字。

StringBuffer的拼接:直接进行append拼接,有syn关键字。

所以速度上StringBuilder>StringBuffer>String

如果我们已经能预知到字符串的长度,可以在创建的时候就给它更大的空间,省去扩容的花费的时间。

StringBuilder默认长度是16,扩容是2倍。

可以通过用参数是整形的构造器来让它初始化指定大小的空间。

public StringBuilder(int capacity) {
    super(capacity);
}

四、String相关面试题

  1. 在字符串常量池中没有以下创建的字符串,String s = new String(“ab”)和String s = new String(“a”) + new String(“b”)分别创建了几个对象:

    答:前者创建了两个对象,在堆中创建对象,然后在池中创建"ab",再让堆中的对象指向池中的"ab"字符串。后者先是先创建一个StringBuilder对象①,再创建String对象②,再在池中创建字符串"a"③,让②指向③,再创建第二个String对象④与字符串"b"⑤,最后通过StringBuilder中的append()操作后,返回使用toString()返回。在这个toString中实际上是返回new String(value,0,count)⑥,因此是创建了6个对象。

    注意:StringBuilder的toString()是通过传递value(byte或者char数组)创建的,所以并不会再去字符串常量池中添加。

    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
    
  2. 关于String中的intern的问题:

    分析以下代码输出的内容分别是什么:

    public class StringInternTest {
        public static void main(String[] args) {
            String s1 = new String("a");
            s1.intern();
            String s2 = "a";
            System.out.println(s1 == s2);//false
    
            String s3 = new String("a") + new String("b");
            s3.intern();
            String s4 = "ab";
            System.out.println(s3 == s4);//在jdk6及之前为false,jdk7及之后为true
    
            String s5 = new String("cd");
            s5 = s5.intern();
            String s6 = "cd";
            System.out.println(s5 == s6);//true,因为intern()方法返回的是常量池的对象
    
            String s7 = new String("c") + new String("d");
            s7.intern();
            System.out.println(s6 == s7);//false,因为在进行创建s5的同时将"cd“加入常量池,所以此时情况跟s1、s2一致
        }
    }
    

    因为画的有点乱所以就先分析下s1、s2以及s3、s4的内存结构

在这里插入图片描述

总结:

  • jdk1.6中,将这个字符串对象尝试放入串池
    • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
    • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
  • jdk1.7起,将这个字符串对象尝试放入串池。
    • 如果串池中有,则并不会放入。返回已有的串池中的对象的地址
    • 如果没有,则会把对象的引用地址复制一份,放入串池,并返回串池中的引用地址
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值