java基础 浅析string,StringBuffer与StringBuilder

常用的类出来八个基本数据,就是string。而string类在程序编写中是无法避免的的一个类,很多时候程序中都需要对string进行处理。所以本章聊一下string以及StringBuffer和 StringBuilder。

string

String 类代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。

字符串是常量;它们的值在创建之后不能更改。

上面两句话是摘自官方文档,意思是string是字符串类型的数据,同时string数据是无法改变的,也就是在其定义赋值的时候就确定。

不过这个时候就有疑问了为什么string是不可改变的,还有那对string数据进行修改难道不是数据的改变吗?

带着这些疑问我们需要先看一下string的源码,然后进行查看。
在这里插入图片描述
这个地方可以看出两点

  • string的数据存储是一个char类型的数组。
  • 为什么不可改变因为char数组前面又关键字final。(这个在接口中讲过:其修饰的变量就是代表不可变。当然不可变不是绝对的,反射可以修改其值)

既然不可变,那么问题就来了那就是两个字符串是如何连接的?所以可以看其源码,当然string中连接方法是concat方法,所以我们先看代码:

    /**
     * Concatenates the specified string to the end of this string.
     * <p>
     * If the length of the argument string is {@code 0}, then this
     * {@code String} object is returned. Otherwise, a
     * {@code String} object is returned that represents a character
     * sequence that is the concatenation of the character sequence
     * represented by this {@code String} object and the character
     * sequence represented by the argument string.<p>
     * Examples:
     * <blockquote><pre>
     * "cares".concat("s") returns "caress"
     * "to".concat("get").concat("her") returns "together"
     * </pre></blockquote>
     *
     * @param   str   the {@code String} that is concatenated to the end
     *                of this {@code String}.
     * @return  a string that represents the concatenation of this object's
     *          characters followed by the string argument's characters.
     */
    public String concat(String str) {
        int otherLen = str.length();//otherLen是需要连接目标字符串的长度
        if (otherLen == 0) {
            return this;//如果长度为0,那就是直接返回this自己即可。
        }
        int len = value.length;// len 就是本身字符串中的char的长度
        char buf[] = Arrays.copyOf(value, len + otherLen);
        //Arrays.copyOf(value, len + otherLen)方法是复制字char[]数组,长度是两个字符串长度之和。
        //Arrays.copyOf 如果目标数组没有长度长,可以赋值其类型的默认值。
        str.getChars(buf, len);//这个方法就是将目标字符串放入buf这个char数组
        return new String(buf, true);//返回一个新的string
    }


    /**
     * Copy characters from this string into dst starting at dstBegin.
     * This method doesn't perform any range checking.
     */
    void getChars(char dst[], int dstBegin) {
        System.arraycopy(value, 0, dst, dstBegin, value.length);
    }
// 这个方法的参数值dst的值为buf。dstBegin是指原来字符串char的长度
//System.arraycopy(value, 0, dst, dstBegin, value.length);中的参数讲解 这个时候value值就不是原来的char数组了,而是目标字符串的char数组。 value.length也就是目标字符串的char[]数组长度 因为value其实是this.value

通过源码我们可以看出了string字符串相互连接的时候,其实就是复制一个char数组长度是两者之和,然后再通过new stirng(char[])构造方法重新新建一个string
在这里插入图片描述
这个时候估计有人疑问,因为数组长度不可变,所以新建了一个string,但是如果我不变长度,直接修改其中一个char的值其实就不用新建一个string对象了?(这种想法其实有点忽略final关键字,不过我们可以看源码)

  /**
     * Returns a string resulting from replacing all occurrences of
     * {@code oldChar} in this string with {@code newChar}.
     * <p>
     * If the character {@code oldChar} does not occur in the
     * character sequence represented by this {@code String} object,
     * then a reference to this {@code String} object is returned.
     * Otherwise, a {@code String} object is returned that
     * represents a character sequence identical to the character sequence
     * represented by this {@code String} object, except that every
     * occurrence of {@code oldChar} is replaced by an occurrence
     * of {@code newChar}.
     * <p>
     * Examples:
     * <blockquote><pre>
     * "mesquite in your cellar".replace('e', 'o')
     *         returns "mosquito in your collar"
     * "the war of baronets".replace('r', 'y')
     *         returns "the way of bayonets"
     * "sparring with a purple porpoise".replace('p', 't')
     *         returns "starring with a turtle tortoise"
     * "JonL".replace('q', 'x') returns "JonL" (no change)
     * </pre></blockquote>
     *
     * @param   oldChar   the old character.
     * @param   newChar   the new character.
     * @return  a string derived from this string by replacing every
     *          occurrence of {@code oldChar} with {@code newChar}.
     */
    public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

具体算法就是如上 不过我们也可以看到一个 return new String(buf, true);所以也是新建了一个元素。除非是(oldChar = newChar) 才会不新建对象,直接返回this。

知道了string的一些原理,不过我们需要再进一步的了解其再jvm中存在。不然就会对==和equals直接的区别有些模糊。以及string字符串通过**+**号连接有什么什么区别。以及new string(字符串)和=字符串有有什么区别。

这个首先就要明白一个事情,那就是string值存在的位置,无法避免的一个jvm中的存储区域常量池。常量池的位置其实随着版本的变化,其存在的位置再不停的变化,但是其所拥有的意义还是一样的。

常量池:常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式;当然也可扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。

其拥有的特点:

  • 1:常量池存储着常量数据,包括其类,方法,以及接口中的常量等。
  • 2:常量池中的数据都是唯一的。

jdk1.6如下
在这里插入图片描述
jdk1.7 如下

在这里插入图片描述
jdk1.8 其中常量池在元空间中。
在这里插入图片描述
其实三个图都是一个大概的存储结构,而对于jvm的结构划分,如果需要会单独出一篇。目前先这样简单看一下结构理解一下。

讲解string字符串看了源码,不先开始讲解其方法而讲这个结构也是有原因的,因为其中在string中常用来区别或者解释==和equals区别,以及在创建一个string对象的时候创建了几个对象等问题。所以我们需要对其结构进行一个简单的了解才可以理解。

先看代码,我们先讲解一下==这个逻辑符合再string中的效果

public class Test  {

	public static void main(String[] args) {

		String a="hello";
		String b="hello";

		String  a1=new String("hello");
		String  b1=new String("hello");
		
		System.out.println("a==b  "+(a==b));
		System.out.println("a1==b1 "+(a1==b1));
		String c="hello"+" world";
		String d="hello"+" world";
		
		
		String  c1=new String("hello")+" world";
		String  d1=new String("hello")+" world";
		
		System.out.println("c==d   "+(c==d));
		System.out.println("c1==d1  "+(c1==d1));

        String e="hello wor"+"ld";
        String f=a+" world";
		
		System.out.println("c==e   "+(c==e));
        System.out.println("c==f   "+(c==f));
    }
}

//输出
a==b  true
a1==b1 false
c==d   true
c1==d1  false
c==e   true
c==f  false

看到这个结构是不是有些不敢相信。其实对比的是两个数据所在jvm中的地址,不但值要一样,而且地址也要一样。这个用图说明,毕竟方便,所以我们直接用jdk1.6的结构图来讲解。
在这里插入图片描述
看图应该可以理解a,b与a1,b1在声明变量的生活两者的区别。看图我们应该明白常量池中存在的数据值是唯一的,而为什么a1,b1的地址不同,是因为a1的value与b1value的存在堆中地址不一样所以使用
的生活会返回false。

而直接通过=给string赋值,和通过new的区别不同,而是直接指向常量池中的数据。需要先在堆中先建立一个对象。

两个字符串值通过 “hello”+" world" 连接其实"hello world"一样,直接指向常量池中的数据。但是如果参数了声明的变量a+" world" 就不会直接指向常量池了,而是等于和new一个对象然后相加的效果一样。

现在我举出一个例子。最好先不要看结果,而是自己先猜测一下,然后再看结果

public class Test  {

	public static void main(String[] args) {

		final String a="hello";
	
		String b="hello"+" world";
		String c=a+" world";
		

		
		System.out.println("c==b  "+(c==b));

		
	}
}





//输出
c==b  true


有人会看到这个结果,然后看了一下标红的总结,觉得是两个矛盾,不过本质又无区别,只是那句话没有考虑全部情况。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。

上面我对比字符串的时候用的==两个等于号,其对比的是地址,如果字符串内容是否相等,我们使用的是equals()方法,这个我们看一下其源码大概就知道什么原理了。

 /**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
        if (this == anObject) {//如果地址相同,自然会返回true。
            return true;
        }
        if (anObject instanceof String) {//元素是否为string的实例,然后将其字符串蝙蝠char[]数组,然后依次对比。
            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;
    }

如此看明白==和equals方法的区别了,一个是看其引用的地址是否一样,一个是看其内容是否一样。

  • 补充

前面又说值传递和引用传递两个方式。其中值传递不会改变原来变量的值,而引用传递会改变其变量的值(String 类型除外)。

public class Test  {

	public static void main(String[] args) {	
		int a=12;
		String b="main";
		int[] c= {1,2,3};
		System.out.println("main 方法中 前"+a);
		System.out.println("main 方法中前"+b);
		System.out.println("main 方法中前"+Arrays.toString(c));
		System.out.println("=============================");
		
		show(a,b,c);
		
		System.out.println("=============================");
		System.out.println("main 方法中后"+a);
		System.out.println("main 方法中后"+b);
		System.out.println("main 方法中后"+Arrays.toString(c));

	}
	
	private static void show(int a,String b,int[] c) {
		a=24;
		b="show";
		c[2]=5;
		System.out.println("show 方法中"+a);
		System.out.println("show 方法中"+b);
		System.out.println("show 方法中"+Arrays.toString(c));

	}
}
//输出
main 方法中 前12
main 方法中前main
main 方法中前[1, 2, 3]
=============================
show 方法中24
show 方法中show
show 方法中[1, 2, 5]
=============================
main 方法中后12
main 方法中后main
main 方法中后[1, 2, 5]

这个可以看出string 的一个特点就是其不可变性,string虽然是引用类型,但是其实参不会因为形参值的变化而变化。形参重新赋值以后,有string源码可以知道等于新建了一个string。

因为string是程序中最常用的声明变量之一,下面可以看一下其常用的方法。我们直接用代码演示吧。

public class Test  {

	public static void main(String[] args) {
    String a="abcdefg";
    System.out.println(a.length());//7    length()  返回字符串的长度
    System.out.println(a.concat("123"));//  abcdefg123   
    System.out.println(a);// abcdefg       concat("123") 连接两个字符串,同时返回两个字符串连接后的值,但是其原来的值不变
    System.out.println(a.contains("m"));//false   contains() 查看字符串中是否包含字符或字符串,如果包含返回true,反之返回false
    System.out.println(a.isEmpty());//false   isEmpty()判断字符串是否为空,如果为空返回true,反之返回false
    
    
    String b="bcdefgabcd";
    System.out.println(b.indexOf("a"));//6   indexOf()  如果字符串含有这个字符或字符串返回第一个所在的位置,如果没有返回-1
    System.out.println(b.indexOf("b",5));//7   indexOf(str, index)  如果字符串从index之后含有这个字符或字符串返回第一个所在的位置,如果没有返回-1
    
    System.out.println(b.lastIndexOf("b1"));//7     lastIndexOf()字符串包含某字符和字符串的最后一个所在的位置,如果没有返回-1
    System.out.println(b.lastIndexOf("b",5));//0    lastIndexOf(str,index)字符串在index自强包含某字符和字符串的最后一个所在的位置,如果没有返回-1
    
    
    System.out.println(b.startsWith("a"));//false     startsWith() 判断字符串是否以某字符串开头,如果是就返回true ,反之返回false
    System.out.println(b.startsWith("a",6));//true     startsWith(str.index) 判断字符串在index处是否以某字符串开头,如果是就返回true ,反之返回false
    
    System.out.println(b.endsWith("cd"));//true    endsWith() 字符串是否以某字符或者字符串结尾,如果是返回true,如果不是返回false。
    
    System.out.println(b.substring(4));// fgabcd  substring()字符串冲index出开始街区字符串到最后并返回
    System.out.println(b.substring(4,8));// fgab  substring(start,end)字符串冲index出开始街区字符串到end,并返回。(一般返回都是包含前而不包含后,所以substring(start,end)也不例外)
    
    
    System.out.println(b.equals("bcdefgabcd"));//true  equals() 对比两个字符串的内容是否一样。
    
    
    System.out.println(b.charAt(0));//b  charAt(index) 返回在字符串index的字符。
    System.out.println(Arrays.toString(b.toCharArray()));//[b, c, d, e, f, g, a, b, c, d]     toCharArray()将此字符串转换为一个新的字符数组。
    
    System.out.println(b.replace("cd", "kk"));//bkkefgabkk  replace(char oldChar, char newChar)    返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
// replaceAll(String regex, String replacement) 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
    
    System.out.println(Arrays.toString(b.split("c")));// [b, defgab, d]   split() 根据给定正则表达式的匹配拆分此字符串。 
    System.out.println(Arrays.toString(b.split("c",2)));// [b, defgabcd]  split(String regex, int limit)  根据给定正则表达式的匹配拆分此字符串。 但是数组长度只能为limit,超过的就不再拆分
    System.out.println(Arrays.toString(b.getBytes()));   // [98, 99, 100, 101, 102, 103, 97, 98, 99, 100] 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
    try {
		System.out.println(Arrays.toString(b.getBytes("gbk")));//[98, 99, 100, 101, 102, 103, 97, 98, 99, 100] getBytes() 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
	} catch (UnsupportedEncodingException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
    
//       上面两个的编码可以通过 String(byte[] bytes, String charsetName) 进行解码。
    
    String c="  df  df  ";
    System.out.println(c.trim());//df  df trim() 只会去掉 收尾两处的空格,中间的不会去掉并返回字符串
    
    System.out.println(String.valueOf(2345));//2345   valueOf(boolean b)  其他类型转成string
    	
	}

}
	

String 是不可变的,自然也有可变的字符串stringbuffer和stringbuilder。

StringBuffer和 StringBuilder

两个是可变的字符串,但是两者又有所区别

StringBuffer:线程安全,效率相对于StringBuilder低。

StringBuilder:线程不安全,效率相对于StringBuffer高。

 * @author      Arthur van Hoff
 * @see     java.lang.StringBuilder
 * @see     java.lang.String
 * @since   JDK1.0
 */
 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence


     
     
 * @author      Michael McCloskey
 * @see         java.lang.StringBuffer
 * @see         java.lang.String
 * @since       1.5
 */
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence 

通过源码可以看出stringbuffer和stringbuilder 两者的父类以及实现的接口完全一样。以及stringbuilder是1.5版本出现的,而stringbuffer是1.0版本出现的。

因为父类和接口一样,所以两者的方法几乎一样。所以我们当然既然一个安全,一个不安全还是有区别的,这个我们在看源码的时候在详解。

所以我们先看stringbuffer的源码,然后看其与string的实现区别。

  /**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */
    public StringBuffer() {
        super(16);
    }

//可见其默认实例调用了父类的构造方法
    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
//这个地方可以看出其默认会创建一个长度为16的字符数组

然后看起添加字符串的方法,查看其在源码中是如何实现的。

   @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);//调用父类的方法
        return this;//返回其本身,不像string返回一个新建的对象。
    }
//


//父类中的方法

 /**
     * Appends the specified string to this character sequence.
     * <p>
     * The characters of the {@code String} argument are appended, in
     * order, increasing the length of this sequence by the length of the
     * argument. If {@code str} is {@code null}, then the four
     * characters {@code "null"} are appended.
     * <p>
     * Let <i>n</i> be the length of this character sequence just prior to
     * execution of the {@code append} method. Then the character at
     * index <i>k</i> in the new character sequence is equal to the character
     * at index <i>k</i> in the old character sequence, if <i>k</i> is less
     * than <i>n</i>; otherwise, it is equal to the character at index
     * <i>k-n</i> in the argument {@code str}.
     *
     * @param   str   a string.
     * @return  a reference to this object.
     */
    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;
    }

//看代码需要知道两个参数的意思如下
   /**
     * The value is used for character storage.
     */
    char[] value;//存储字符数组

    /**
     * The count is the number of characters used.
     */
    int count;//字符数组中存的字符个数


下面是判断修改字符数组的长度的代码

    /**
     * For positive values of {@code minimumCapacity}, this method
     * behaves like {@code ensureCapacity}, however it is never
     * synchronized.
     * If {@code minimumCapacity} is non positive due to numeric
     * overflow, this method throws {@code OutOfMemoryError}.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {//minimumCapacity 为字符数组中包含字符个数加上要添加的字符串的长度。    然后判断minimumCapacity是否大于存储字符的字符数组长度。
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
  

rrays.copyOf() 我们知道复制value字符串。然后给一个新的长度,然后数据不够补充默认值。所以这个看起变长需要看 newCapacity(minimumCapacity)

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;//新的数组长度是原来数组长度的2倍+2
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;  //如果原来字符个数+新增字符串长度大于2倍元字符数组的,则返回长度为两者之和
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)//三目运算如果新长度为零或者大于字符数组最大长度那么就调用hugeCapacity(minCapacity)否则返回新长度
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }
  /**
     * The maximum size of array to allocate (unless necessary).
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
   private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // 如果新长度大于 Integer.MAX_VALUE - 8;就会抛出异常
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }


通过上面的源码我们可以看出stringbuffer也是以数组作为存储的,以及其改变长度后依然返回自己,return this。而string返回的是 new string(新字符串数组)。

string实例后只要改变后,其引用的数组就会改变(如果长度不变的话,其引用数组不会改变,只是会改变其存储的数据。),然后引用的数组就会创建一个新的对象。

stringbuffer实例后改变,其引用的字符数组也会改变(如果长度不变的话,其引用数组不会改变,只是会改变其存储的数组。),然后实例引用新的数组。

因此可见其string是比较占用内存,以及效率低于stringbuffer的。因为string每次都需要创建新的对象。

明白其原理,所以现在看一下其构造的方法。

StringBuffer() 构造一个其中不带字符的字符串缓冲区,初始容量为 16 个字符。 
StringBuffer(CharSequence seq)  构造一个字符串缓冲区,它包含与指定的 CharSequence 相同的字符。 
StringBuffer(int capacity) 构造一个不带字符,但具有指定初始容量的字符串缓冲区。 
StringBuffer(String str)   构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容

一般创建的时候使用默认实例化,但是如果知道要创建的长度,就直接带有长度创建,这样可以节省内存。

常用方法

public class Test  {

	public static void main(String[] args) {
     StringBuffer strbuf= new StringBuffer();//构造方法。
     strbuf.append("abc");
     System.out.println(strbuf.length());//3   length()  stringbuffer的长度
     System.out.println(strbuf.capacity());//16  capacity() 得到stringbuffer生成的char数组的容量
     
     
     
     System.out.println(strbuf.append(12));//abc12   append()   可以添加字符串以及数字浮点类型,返回添加后的StringBuffer
     System.out.println(strbuf);//abc12
     
     System.out.println(strbuf.insert(3,"A"));//abcA12   insert(offset,obk)   可以添加数据在offset处,返回添加后的StringBuffer
     char[] str= {'H','M','G'};
     System.out.println(strbuf.insert(0, str, 1, 2));//MGabcA12   insert(index, str, offset, len)  插入一个char数组,从char数组第offset开始,长度为len。返回添加后的StringBuffer
     
     System.out.println(strbuf.deleteCharAt(2));  //MGbcA12  deleteCharAt(index) 删除index出的字符,返回stringbuffer
     System.out.println(strbuf);// MGbcA12
     
     System.out.println(strbuf.delete(2, 70));// MG   delete(start, end) 删除这个范围内的字符,如果end大于stringbuffer的长度,不会报错在源码中会变成stringbuffer的长度,返回stringbuffer
     System.out.println(strbuf);// MG  
     
     
     strbuf= new StringBuffer("abcdab");//重新实例化一个对象
     
     System.out.println(strbuf.indexOf("b"));//1  indexOf(str)  字符或字符串在stringbuffer中的第一位置,如果没有返回-1
     System.out.println(strbuf.indexOf("b",3));//5   indexOf(str,index)   字符或字符串在stringbuffer中从index开始的第一位置,如果没有返回-1
     System.out.println(strbuf.lastIndexOf("b"));//5   lastIndexOf(str)  字符或字符串在stringbuffer中的最后的位置,如果没有返回-1
     System.out.println(strbuf.lastIndexOf("b", 1));//1   lastIndexOf(str,index) 字符或字符串在stringbuffer中的从开始到index中最后的位置(包含index),如果没有返回-1
     
     System.out.println(strbuf.reverse());// badcba  reverse() 将strbuffer中的字符串反转
     System.out.println(strbuf);//badcba   这可以看出reverse()反转的是本体
     
     System.out.println(strbuf.replace(0, 3, "ABCD"));//ABCDcba    strbuf.replace(start, end, str)将str替换stringbufff中的start到end的字符
     System.out.println(strbuf);//ABCDcba
     
     strbuf.setCharAt(0, 'G');//setCharAt (index,chr)  chr替换了stringbuffer中index位置的字符
     System.out.println(strbuf);// GBCDcba
     
     System.out.println(strbuf.substring(3));//Dcba  substring(index) 截取stringbuffer中字符从index开始,然后返回截出的字符串
     System.out.println(strbuf);// GBCDcba
     System.out.println(strbuf.substring(2, 4));//CD  substring(star,end) 截取stringbuffer中字符从star开始到end,然后返回截出的字符串
	
	}

}
	

stringbuilder的方法和stringbuffer中的方法使用常用的方法一样,所以不再单独展示。既然一样那么其安全和不安全的原因是什么呢?

 //stringbuffer   中的方法

@Override
    public synchronized StringBuffer append(int i) {
        toStringCache = null; //
        super.append(i);
        return this;
    }


//stringbuilder 中的方法
  @Override
    public StringBuilder append(int i) {
        super.append(i);
        return this;
    }

对append方法可以看出两点

  • stringbuffer安全的原因是其方法由synchronized修饰。而stringbuilder的方法却没有synchronized修饰

  • 可以看出,StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。

    而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。

    所以,缓存冲这也是对 StringBuffer 的一个优化吧,不过 StringBuffer 的这个toString 方法仍然是同步的。

既然我们前面一直说string与stringbuffer,stringbuilder的区别,也说了其运行速度,下面我们做一个代码测试即可。

public class Test  {

	public static void main(String[] args) {
		String str=new String();
		long start =System.currentTimeMillis();
		for(int i=0;i<10000;i++) {
			str=str+i;
		}
		long end=System.currentTimeMillis();
		System.out.println("String 的时间"+(end-start));

		StringBuffer strbuf=new StringBuffer();
		start =System.currentTimeMillis();
		for(int i=0;i<10000;i++) {
			strbuf.append(i);
		}
		end=System.currentTimeMillis();
		System.out.println("StringBuffer 的时间"+(end-start));

		StringBuilder strbud=new StringBuilder();
		start =System.currentTimeMillis();
		for(int i=0;i<10000;i++) {
			strbud.append(i);
		}
		end=System.currentTimeMillis();
		System.out.println("StringBuilder 的时间"+(end-start));
	


	}

}
//输出
String 的时间278
StringBuffer 的时间2
StringBuilder 的时间1

可以看出运行速度StringBuilder>StringBuffer>String.

区别

  • string 是对象而非基本数据,其是final类不可以继承,而其创建的时候就不可变。对于其修改都是新建一个string值。
  • StringBuilder,StringBuffer 也是对象,其值可变。其也是final也不可以继承。而两者操作字符串运行速度都比string快
    • StringBuilder线程不安全,如果单例模式的话可以用次类来操作字符串增加运行速度。不可用于多线程。
    • StringBuffer 线程安全多线程下操作字符串可以用StringBuffer 。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值