zoukankan      html  css  js  c++  java
  • String总结

    1.String的特性

    1.1 不可变性

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    
    • 查看源码可知String类由final修饰且底层存储String的底层数据结构char数组也是final修饰
    • jdk9以后char数组改为byte数组
      • 官方解释大部分情况下一个byte可以存储一个字符(英文、拉丁文),用char浪费 http://openjdk.java.net/jeps/254
      • 基于String的相关类如AbstracString、StringBulder、StringBuffer、甚至jvm内置的String结构也同样修改成了byte
    • 字面量形式的字符串定义在字符串常量池中,字符串常量池中的字符串具有唯一性
    • new String("")定义的字符串不出现在字符串常量池中,但在堆中有对应的内存空间存储对象
    • 字符串常量池底层HashTable
      • 用来保证字符串不重复
      • jdk及6以前默认长度是1009,jdk7以后默认大小是60013
      • 使用 -XX:StringTableSize可设置StringTable的长度
      • 如果放入String Pool 的String非常多,会造成Hash冲突严重,从来导致链表会很长,而链表长了会响应性能,比如String.intern性能会大幅下降

    1.2 内存结构

    • 直接使用双引号声明出的String对象会直接存储在常量池中
      比如 String info = "hello";
    • 如果不是双引号声明的对象,可以使用String提供的intern()方法将String对象放入常量池
    • jdk6及以前,字符串常量池存放在永久代。
    • jdk7开始 字符串常量池的位置调整到了java堆内
    • jdk8 元空间取代永久代,常量池依然在java堆内
    • 为什么要调整位置
      • 永久代默认比较小
      • 永久代垃圾回收频率低

    1.3 字符串拼接

    • 常量与常量的拼接结果在常量池,原理是编译器优化
    • 常量池中不会存在相同内容的常量
    • 只要其中一个是变量,结果就在堆中,变量拼接的原理是StringBuiler
    • 如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象地址
    • 多个字符串拼接要用StringBuilder(线程安全用StringBuffer)会提高效率节省空间,直接加号拼接每一次加号都会new一个StringBuilder浪费时间和内存空间
    • 如果确定最终拼接后不高于一个值,直接new StringBuilder(X),效率会更高,因为底层不需要空间不够进行扩容了
        @Test
        public void testAppend() {
            String s1 = "a" + "b" + "c";
            String s2 = "abc";
            System.out.println(s1 == s2);//true
            System.out.println(s1.equals(s2));//true
        }
    
        @Test
        public void testAppend2() {
            String s1 = "javaEE";
            String s2 = "hadoop";
            String s3 = "javaEEhadoop";
            String s4 = "javaEE" + "hadoop";
            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(s5 == s6); // false
            System.out.println(s5 == s7); // false
            System.out.println(s6 == s7); // false
            String s8 = s6.intern();
            System.out.println(s3 == s8); // true
        }
    
        @Test
        public void testAppend3() {
            String s1 = "a";
            String s2 = "b";
            String s3 = "ab";
            /**
             * s1 + s2 的细节 1.StringBuilder s = new StringBuilder(); 2.s.append("a"); 3.s.append("b");
             * 4.s.toString(); --> 类似于 new String();
             */
            String s4 = s1 + s2;
            System.out.println(s3 == s4); // false
        }
    
     /**
         * 1. 字符串拼接不一定适用StringBuilder
         * 如果拼接符号左右两边都是字符串常量或常量引用,则仍然适用编译器优化,即非StringBuilder的方式
         * 2. 针对于final修饰的类、方法、基本数据类型、引用数据类型的时候能用上建议用上
         */
        @Test
        public void testAppend4() {
            final String s1 = "a";
            final String s2 = "b";
            String s3 = "ab";
            String s4 = s1 + s2;
            System.out.println(s3 == s4); // true
        }
    

    1.4 intern()方法

     /**
         * Returns a canonical representation for the string object.
         * <p>
         * A pool of strings, initially empty, is maintained privately by the
         * class {@code String}.
         * <p>
         * 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.
         * <p>
         * 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}.
         * <p>
         * All literal strings and string-valued constant expressions are
         * interned. String literals are defined in section 3.10.5 of the
         * <cite>The Java&trade; Language Specification</cite>.
         *
         * @return  a string that has the same contents as this string, but is
         *          guaranteed to be from a pool of unique strings.
         * @jls 3.10.5 String Literals
         */
        public native String intern();
    
    • native 方法,底层是C++ 实现的StringTable相关代码
    • 如果常量池存在,返回地址
    • 如果常量池存在,放入常量池返回地址

    1.5 String的垃圾回收

    2.String的经典面试题

    2.1

    public class StringExer {
    
        String str = new String("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.str, ex.ch);
            System.out.println(ex.str);
            System.out.println(ex.ch);
        }
    }
    
    • 执行结果
    good
    best
    
    • 原因
      • change方法传递过去的都是引用
      • 由于Stirng的不可变性,str="test ok" 会在字符串常量池中重新生成一个字符串,参数str会重新记录一个新的引用地址,但不会改变成员变量str的地址和值
      • ch的改变不会改变地址,由于成员变量ch和参数ch指向的都是同一个地址,对其改变值不会改变地址,所以成员变量会跟着改变成best

    2.2

    • new String("ab")会创建几个对象?
      • 字符串常量池中"ab"
      • 堆中的一个非常量池的对象 new 出来的
    • new String("a") + new String("b")会创建几个对象
      • 字符串常量池中 "a"
      • 字符串常量池中 "b"
      • new String("a")
      • new String("b")
      • StringBuilder
      • StringBuilder最后的newString();

    2.3

    public class StringIntern {
        public static void main(String[] args) {
            String s = new String("1");
            s.intern();
            String s2 = "1";
            System.out.println(s == s2);
            String s3 = new String("1") + new String("1");
            s3.intern();
            String s4 = "11";
            System.out.println(s3 == s4);
        }
    }
    
    • 执行结果
      • jdk 6 :
    false
    false
    
    • jdk7+:
    false
    true
    
    • 原因:
      • 第一个false:
        s指向的是对象地址(非字符串常量),s2指向的字符串常量池"1"的地址,所以false很容易理解
      • jdk6
        s3是常量池外的一个对象地址,s4就是字符串常量池中"11"的地址,所以false
      • jdk7+
        s3是常量池外的一个对象地址,s3.intern() 其实返回的就是s3的地址,s4自然也是s3的地址,因为这时s3.intern不再单纯记录字符串常量,如果对象直接有一样的值常量池会记录对应的地址

    2.4 深入理解java虚拟机原题

    String s1 = new StringBuilder("hell").append("o").toString();
    System.out.println(s1 == s1.intern());
    String s2 =  new StringBuilder("ja").append("va").toString();
    System.out.println(s2 == s2.intern());
    
    • 答案(jdk7+)
    true
    false
    
    • 原因
      • 第一个true 好理解,因为s1.intern()时,字符串常量池记录的直接就是s1的地址(jdk7+)所以结果为true
      • 第二个和第一几乎一样的代码为啥是false?其实"java"这个字符串在jvm启动时就加载了,所以s2.intern()返回的是一开始加载的“java”这个字符串常量池的地址,而s2就是一个字符串常量池外的一个对象的地址,所以二者不同
  • 相关阅读:
    Web前端工程师技能列表
    CSS框架的相关汇总(CSS Frameworks)
    一个有趣的发现
    (转丁学)Firefox2的一个bug和脑子进了水的IE
    深入语义:列表Tag(ul/ol)和表格Tag(table)的抉择
    css命名简单框架
    腾讯的三栏布局考题
    土豆网前端概况
    伪绝对定位(译)
    右下角浮动广告代码DEMO
  • 原文地址:https://www.cnblogs.com/zhucww/p/14011703.html
Copyright © 2011-2022 走看看