• java基础解析系列(一)---String、StringBuffer、StringBuilder


    java基础解析系列(一)---String、StringBuffer、StringBuilder

    前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容。

    我的博客目录

    String

    ==问题

            String s6=new String("jiajun");
            String s1="jiajun";
            String s2="jiajun";
            System.out.println(s1==s2);//true
            System.out.println(s1==s6);//false
    
    • 看常量池中是否已有此字符串,如果有,将指针指向这个字符串
    • 如果使用new来创建字符串对象,那么这个字符串是存放在堆中,无论堆中是否已有这个对象

    String对象改变

    public String substring(int beginIndex) {
            if (beginIndex < 0) {
                throw new StringIndexOutOfBoundsException(beginIndex);
            }
            int subLen = value.length - beginIndex;
            if (subLen < 0) {
                throw new StringIndexOutOfBoundsException(subLen);
            }
            return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
        }
        
        public String concat(String str) {
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            int len = value.length;
            char buf[] = Arrays.copyOf(value, len + otherLen);
            str.getChars(buf, len);
            return new String(buf, true);
        }
    
    • 从源码可以看出,任何操作都是创建一个新的对象,不影响原对象

    StringBuffer和StringBuidler

    初始容量

    • StringBuilder和StringBuffer的构造参数来初始化容量
    public StringBuilder() {
            super(16);
        }
    AbstractStringBuilder(int capacity) {
            value = new char[capacity];
        }
    
    • 默认情况下容量为16
    public AbstractStringBuilder append(String str) {
            if (str == null)
                return appendNull();
            int len = str.length();
            ensureCapacityInternal(count + len);
            str.getChars(0, len, value, count);
            count += len;
            return this;
        }
        private void ensureCapacityInternal(int minimumCapacity) {
            // overflow-conscious code
            if (minimumCapacity - value.length > 0) {
                value = Arrays.copyOf(value,
                        newCapacity(minimumCapacity));
            }
        }
        public static char[] copyOf(char[] original, int newLength) {
            char[] copy = new char[newLength];
            System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));
            return copy;
        }
    
    • 从源码看出,在执行append方法的时候,会执行ensureCapacityInternal方法来保证容量,而如果超出容量的话,会重新创建一个char数组,并将旧的字符数组复制到新的字符数组

    线程安全

    public synchronized StringBuffer append(StringBuffer sb) {
            toStringCache = null;
            super.append(sb);
            return this;
        }
     public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);
            return this;
        }
        
    
     public StringBuilder append(StringBuffer sb) {
            super.append(sb);
            return this;
        }
        public StringBuilder append(String str) {
            super.append(str);
            return this;
        }
        public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
    
    • 可以看出,String的方法是加了synchronzied,也就加了锁,那么而在单线程的情况下或者不用考虑线程安全的情况下,那么StringBuilder的性能是更高的

    toString方法

     public synchronized String toString() {
            if (toStringCache == null) {
                toStringCache = Arrays.copyOfRange(value, 0, count);
            }
            return new String(toStringCache, true);
        }
    public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
    
    • 通过源码发现,toString方法会创建一个新的String对象

    性能试验

    间接相加和直接相加

    public class d {
    	public static void main(String[] args) {
    		String s="I"+"love"+"jiajun";
    		String s1="I";
    		String s2="love";
    		String s3="jiajun";
    		String s4=s1+s2+s3;
    	}
    }
    

    • 通过反编译的结果可以看出,第一种方式字符串直接相加,在编译器就直接优化了”Ilovejiajun“
    • 而第二种方式间接相加,从结果可以看出,是先创建一个StringBuilder,然后再apend,最后再toString方法,可以发现性能比第一种低
    public class d {
    	public static void main(String[] args) {
    		String s="I"+"love"+"jiajun";
    		String s1="I";
    		String s2=s1+"lovejiajun";
    		System.out.println(s==s2);
    	}
    }
    

    • 同样从反编译的结果可以看出,第二种方式并没有被优化,也是通过StringBuilder来实现的,最后通过toString方法创建一个String对象,所以返回的false
    • 但是当s1是用final修饰的却是不一样的,虚拟机会对其进行优化,所以不会像之前一样创建一个StringBuilder,最后在堆中产生一个对象
    public class d {
    	public static void main(String[] args) {
    		
    		final String s1="I";
    		
    		String s2=s1+"lovejiajun";
    	    String s3="Ilovejiajun";
    	    //s1==s3
    	}
    }
    

    用+和用append

    public class Demo3 {
    	public static void main(String[] args) {
    		run1();
    		run2();
    	}
    	
    	public static void run1() {
            long start = System.currentTimeMillis();
            String result = "";
            for (int i = 0; i < 10000; i++) {
                result += i;
            }
            System.out.println(System.currentTimeMillis() - start);
        }
    
        public static void run2() {
             long start = System.currentTimeMillis();
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < 10000; i++) {
                builder.append(i);
            }
            System.out.println(System.currentTimeMillis() - start);
        }
        //输出:223 1
    
    
    • 从实验发现,用append效率更高,从实验一发现,当字符串相加的时候,实际上每次都会重新初始化StringBuilder然后执行相加,这样效率并不高

    初始化容量

    public class Demo3 {
    	public static void main(String[] args) {
    		test1();
    		test2();
    	}
    
    	public static void test1() {
    		StringBuilder sb = new StringBuilder(7000000);
    		long start = System.currentTimeMillis();
    		for (int i = 0; i < 1000000; i++) {
    			sb.append("jiajun");
    		}
    		long end=System.currentTimeMillis()-start;
    		System.out.println(end);
    	}
    
    	public static void test2() {
    
    		StringBuilder sb = new StringBuilder();
    		long start = System.currentTimeMillis();
    		for (int i = 0; i < 1000000; i++) {
    			sb.append("jiajun");
    		}
    		long end=System.currentTimeMillis()-start;
    		System.out.println(end);
    	}
        //输出:18 26
    }
    
    • 通过实验可以看出,适当的初始化容量可以提高性能,因为当不初始化容量的时候,如果此时append超出容量,那么将会从新创建一个char数组,并且进行复制

    总结

    • 用new创建对象的时候,会在堆中创建对象,而如果是直接用引号形式的话,会先看常量池是否有此字符串,有的话指向常量池的字符串
    • StringBuilder是非线程安全的,StringBuffer是线程安全的
    • 使用StringBuilder和StringBuffer的时候最好初始化一个合适的容量,因为如果默认容量不够的话,会重新创建一个char数组,再进行复制
    • 字符串相加的时候,直接相加的时候,编译器会进行优化,而如果是间接相加的时候,实际上会创建一个StringBuilder来进行append

    我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

    作者:jiajun 出处: http://www.cnblogs.com/-new/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

  • 相关阅读:
    96. 不同的二叉搜索树
    95. 不同的二叉搜索树 II
    94. 二叉树的中序遍历
    93. 复原IP地址
    python-007(用户登录(三次机会重试))
    python-006求1-2+3-4+5.....99的所有数的和
    python006(求1-2+3-4+5.....99的所有数的和)
    python-005(1-100奇数和偶数)
    python-004(while循环)
    python-003(if...elif...else)
  • 原文地址:https://www.cnblogs.com/-new/p/7417915.html
Copyright © 2020-2023  润新知