zoukankan      html  css  js  c++  java
  • 关于String及StringBuilder的几点区别

    直接看示例1:

    public class StringTest{
    	
    	void stringReplace(String strTemp){
    		strTemp=strTemp.replace('l','i');
    	}
    
    	void stringBufferAppend(StringBuffer sbTemp){
    		sbTemp=sbTemp.append('c');
    	}
    
    	public static void main(String[] args){
    		StringTest st=new StringTest();
    		String str=new String("hello");
    		StringBuffer sb=new StringBuffer("hello");
    
    		// String str1=new String("hello");
    		// StringBuffer sb1=new StringBuffer("hello");
    
    		// System.out.println("str.equals(sb)= "+str.equals(sb));
    		// System.out.println("str.equals(str1	)= "+str.equals(str1));
    		// System.out.println("sb.equals(str)= "+sb.equals(str));
    		// System.out.println("sb.equals(sb1)= "+sb.equals(sb1));
    
    		st.stringReplace(str);
    		st.stringBufferAppend(sb);
    		System.out.println("str= "+str+"	sb= "+sb);
    	}
    }
    输出结果:

    str= hello	sb= helloc
    上面的示例无非就是方法传值与传址的问题,因为形参为引用类型,因此实参传递过来的是对象的地址值,问题来了:改变该地址所对应的内容,实参应该也会跟着发生变化,如sb最终结果那样,但str却没有,为何?

    这就是String与StringBuilder的区别:

    字符串的可变与不可变

    JDK_1.8中这样解释:

    Strings are constant; their values cannot be changed after they are created. 
    String buffers support mutable strings. Because String objects are immutable 
    they can be shared.

    String创建的字符串是不可变的,而StringBuilder(或StringBuffer)通过字符缓冲区创建字符串,可变;

    先来解析下String的不可变:

    String s="hello";
    s="world";
    对象s在创建的时候先查看常量池中是否有"hello",有则指向它,没有就创建一个"hello",再指向它,但当重新对s赋值为"world"时,常量池中的"hello"并没有改变,java会重新开辟一个内存存储新值"world",并令s指向"world";

    因此在示例1中,当调用stringReplace()方法时,实参str所存储的地址值传给了形参strTemp后,strTemp指向了常量池中"hello",当对其进行字符替换时,java会重新创建"heiio",并让形参strTemp指向它,因此形参和实参指向了不同的区域,结果自然显而易见;

    另外,String的不可变是由于:

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        private final char value[];//final的作用,使之初始化后不可改变
        ……
    }

    (P.S.虽然value是用final来修饰的,但仍有办法可以直接改变其值,具体可以参见 反射 )

    再来对比下StringBuilder的可变:

    StringBuilder sb=new StringBuilder("hello");
    sb.append("world");
    在创建StringBuilder对象时,java实际上在堆中创建了字符类型数组char[],存储完"hello"后通过append方法在增加"world",并未开辟新的内存区,形参和实参指向同一区域,因此形参对对象内容进行变化,实参也会跟着变化;

    来看下StringBuilder构造器的底层代码:

    public StringBuilder() {
            super(16);
        }
    public StringBuilder(int capacity) {
            super(capacity);
        }
    public StringBuilder(String str) {
            super(str.length() + 16);
            append(str);
    }
    public StringBuilder(CharSequence seq) {
            this(seq.length() + 16);
            append(seq);
    }
    其基类AbstractStringBuilder的构造方法:
    char[] value;
    AbstractStringBuilder(int capacity) {
            value = new char[capacity];
    }
    ……

    可以发现StringBuilder实际创建了一个默认16字符长的char型数组(亦可指定长度):char[] value;

    若为对象赋值时,所存储的字符串长度未超过所定义的char数组长度,则按顺序存储相应字符,若超过所定义的数组长度,则自动扩充其长度,因此说StringBuffer是可变的;

    我们还是继续看append()底层代码比较形象:

    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) {
            if (minimumCapacity - value.length > 0)
                expandCapacity(minimumCapacity);
    }
    void expandCapacity(int minimumCapacity) {
            int newCapacity = value.length * 2 + 2;
            if (newCapacity - minimumCapacity < 0)
                newCapacity = minimumCapacity;
            if (newCapacity < 0) {
                if (minimumCapacity < 0) // overflow
                    throw new OutOfMemoryError();
                newCapacity = Integer.MAX_VALUE;
            }
            value = Arrays.copyOf(value, newCapacity);
    }
    当为StringBuilder对象增加内容时,会先计算所需最小空间:minimumCapacity=count+len;并与原数组长度的2倍+2即newCapacity进行比较,取较大值并为数组value重新赋值,赋值采用的是数组复制的方式进行;

    常见的数组复制有两种方法:一为:Arrays.copyOf(),另一为:System.arraycopy(),差别在于后者可能存在下标越界的问题,其实前者也是通过调用后者的方法来实现的:

    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;
    }

    由于重新为临时数组copy定义了长度newLength,因此复制的时候不会出现越界问题;在数组复制结束后,将临时数组copy赋值给value,使之指向新的区域,完成append()操作 (这里注意:引用变量value的指向发生了变化,但StirngBuilder的指向没有变,只不过其成员变量(恰巧是个引用变量value)的值发生了改变,因此形参sbTemp和sb仍指向同一区域);

    equals方法

    示例1中注释掉的几个equals()语句,结果只有String与String比较时才返回true,这是String与StringBuilder的另一个区别;

    我们要直到equals()方法的返回结果取决于你如何重写的,如直接继承Object的方法,则比较的是变量的值,对于StringBuilder,它并未重写该方法,因此对两个引用变量进行比较自然返回false;而String类重写了该方法,具体如下:

    public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
    可知,若两个引用变量指向同一区域,则自然返回true,若非,则判断anObject是否是String的实例,若是则进行值比较,否则直接返回false;

    效率的差别

    public class StringBuilderTest2{
    	public static void main(String[] args){
                
                StringBuilder sb=new StringBuilder();
                StringBuilder sb1=new StringBuilder(Integer.MAX_VALUE/7);
                String s=null;
                System.out.println(Integer.MAX_VALUE+"
    "+sb1.capacity());
                long start=System.currentTimeMillis(),end;
                for(int i=0;i<1000000;i++){
                    sb.append(1);
                }
                end=System.currentTimeMillis();
                System.out.println("默认初始化SB时循环100w次耗时:"+(end-start));
                for(int i=0;i<1000000;i++){
                    sb1.append(1);
                }
                start=System.currentTimeMillis();
                System.out.println("给定值初始化SB1时循环100w次耗时:"+(start-end));
                
                for(int i=0;i<100000;i++){
                    s+=1;
                }
                end=System.currentTimeMillis();
                System.out.println("String“+”运算10W次耗时:"+(end-start));
            }
    }

    输出:

    2147483647
    306783378//初始化值
    默认初始化SB时循环100w次耗时:35
    给定值初始化SB1时循环100w次耗时:16
    String“+”运算10W次耗时:3211
    可以发现在循环“+”/append()运算时,String类的效率明显低于StringBuilder,为何?

    这里继续使用javap来分析:

    String s=null;
    s+=1;
    0: aconst_null   
    1: astore_1      
    2: new           #2                  // class java/lang/StringBuilder
    5: dup           
    6: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
    9: aload_1       
    10: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    13: iconst_1      
    14: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    17: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    20: astore_1      
    21: return 

    可以看到编译器在解析String"+"运算时,会转换成StringBuilder,调用append()方法,再通过toString()赋值,因此循环运算时,就不断发生创建对象和产生垃圾,这过程消耗了资源,造成了效率的下降;

    另外,我们也可以发现调用StringBuilder构造方法时若给它传入较大整数作为参数,则运算效率也会有明显的提升,这跟前面所提扩容时数组复制有关,给定较大初始缓存区,自然不需要频繁扩容;
    当然,这里顺带提一下,如果直接把String的"+"运算写成形如:s=""+1+1+1……,而不是通过循环来不断调用变量,则编译器会直接求出其字面量,运行时不会再转换为StringBuilder,效率当然就比后者高了;

  • 相关阅读:
    MBProgressHUD 的类扩展方法用法
    ios中webview的高级用法(二)
    ios中webview的高级用法
    ios中UIWebview和asiHttprequest的用法
    ios中UIWebview中加载本地文件
    iOS中判断网络是否联网
    iPhone开发之在UINavigationBar上使用UISegmentedControl制作
    ios真机调试步骤
    uitableview分组的数据2中方式
    ios中开始页面做法
  • 原文地址:https://www.cnblogs.com/lynxz0620/p/4384931.html
Copyright © 2011-2022 走看看