java基础(二) String、StringBuilder、StringBuffer分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lin051124/article/details/80149740

String是java开发中使用得最频繁的类之一,今天就来学习一下String、StringBuilder和StringBuffer这几个类,分析它们的异同点以及了解各个类适用的场景。

  1. 了解String类以及常见的方法
  2. 深入理解String、StringBuffer、StringBuilder
  3. 不同场景下三个类的性能测试

1、了解String类以及常见的方法

String类实现在java.lang包下面,打开这个源码我们可以发现String类是被final修饰的,所以意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。String类只有两个成员变量 value、hash,都是private修饰,源码中并没有提供设置这两个成员变量的方法,所以这点也说明String无法被修改的。
String部分源码
下面再继续看String类的一些方法实现,以substring方法为例,可以看到不是在原有的字符串上进行操作,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。所以,在这里要永远记住一点:
  “对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
这里写图片描述
这里写图片描述
在学习中,你可能对于String是不可变对象总是存有疑惑,看下面代码:

String s = "123456";
System.out.println("s = " + s);
s = "hello world";
System.out.println("s = " + s);

打印结果为:
s = 123456
s = hello world
这里就要区分对象和对象的引用,在上面代码中,首先创建一个String对象s,然后让s的值为“123456”, 然后又让s的值为“hello world”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。

2、了解String、StringBuffer、StringBuilder区别

这个String类提供了数值不可改变的字符串,那么必然需要有可以修改字符串的业务,于是StringBuffer、StringBuilder自然就是提供的字符串可以进行修改,StringBuffer、StringBuilder区别在于涉及到线程安全和非线程安全,StringBuffer是线程安全,而StringBuilder是非线程安全,两者提供的方法都差不多,无非StringBuffer类提供的方法都加上synchronized进行控制线程安全而已。
这里写图片描述
这里写图片描述
那既然StringBuilder、StringBuffer是字符串可变,我们可以查看一下源码,看看到底是如果改变的,StringBuilder、StringBuffer均继承AbstractStringBuilder类,
这里写图片描述
修改字符串的方法append()、delete()、insert()具体实现也是在AbstractStringBuilder类进行处理,以append()方法为例,可以发现都是通过System类的arraycopy方法来实现的,即将原数组复制到目标数组,查看源码如下:

 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));
        }
    }
Arrays.java
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;
    }

3、再来说一下执行速度

在执行速度上,String < StringBuffer < Stringbuilder ,可以先来个简单测试,

public class test {
    private static int time = 50000;
    public static void main(String[] args) {
        testString();
        testStringBuffer();
        testStringBuilder();
        test1String();
        test2String();
    }


    public static void testString () {
        String s="";
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            s += "java";
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
    }

    public static void testStringBuffer () {
        StringBuffer sb = new StringBuffer();
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            sb.append("java");
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
    }

    public static void testStringBuilder () {
        StringBuilder sb = new StringBuilder();
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            sb.append("java");
        }
        long over = System.currentTimeMillis();
        System.out.println("操作"+sb.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
    }

    public static void test1String () {
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            String s = "I"+"love"+"java";
        }
        long over = System.currentTimeMillis();
        System.out.println("字符串直接相加操作:"+(over-begin)+"毫秒");
    }

    public static void test2String () {
        String s1 ="I";
        String s2 = "love";
        String s3 = "java";
        long begin = System.currentTimeMillis();
        for(int i=0; i<time; i++){
            String s = s1+s2+s3;
        }
        long over = System.currentTimeMillis();
        System.out.println("字符串间接相加操作:"+(over-begin)+"毫秒");
    }

}
代码来自http://www.cnblogs.com/dolphin0520/p/3778589.html

执行效果如下:
这里写图片描述

可见效果非常明显,所以平时如果需要对字符串进行操作修改处理,千万要使用StringBuilder、StringBuffer来代替String。那为何产生如此大的差异尼?
这是因为String类是不可变的,即字符串常量,所以每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。这就会对程序运行产生很大的影响,因为当内存中的无引用对象多了以后,JVM的GC进程就会进行垃圾回收,这个过程会耗费很长一段时间,因此经常改变内容的字符串最好不要用 String类的对象。而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
 但是在某些特殊情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 运行速度是远要比 StringBuffer 快的:

String str ="hello" +"world";
StringBuffer sbr = new StringBuffer("hello").append("world");

但是如果要拼接的字符串来自于不同的String对象的话,那结果就不一样了:

String str1 ="hello";
String str2="world";
String str3=str1+str2;

这时候使用StringBuffer的运行速度更快,而这是我们编程时的大部分情况。

对String相关的一些判断

  String a = "hello2";
  String b = "hello" + 2;
  System.out.println((a == b));//true

  String c = b + 2;
  System.out.println((a == c));//false

  //对于被final修饰的变量,会在class文件常量池中保存一个副本,
  // 也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。
  // 那么String e = d + 2;在编译期间就会被优化成:String e = "hello" + 2;
  final String d = "hello";
  String e = d + 2;
  System.out.println((a == e)); true

关于String s = new String(“xyz”);创建了几个String对象问题

两个或一个都有可能,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”不管出现多少遍,都是缓冲区中的那一个。NewString每写一遍,就创建一个新的对象,它使用常量”xyz”对象的内容来创建出一个新String对象。如果以前就用过’xyz’,那么这里就不会创建”xyz”了,直接从缓冲区拿,这时创建了一个StringObject;但如果以前没有用过”xyz”,那么此时就会创建一个对象并放入缓冲区,这种情况它创建两个对象。

猜你喜欢

转载自blog.csdn.net/lin051124/article/details/80149740
今日推荐