java基础 浅析string,StringBuffer与StringBuilder

2024-06-18 21:38

本文主要是介绍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,反之返回falseSystem.out.println(a.isEmpty());//false   isEmpty()判断字符串是否为空,如果为空返回true,反之返回falseString b="bcdefgabcd";System.out.println(b.indexOf("a"));//6   indexOf()  如果字符串含有这个字符或字符串返回第一个所在的位置,如果没有返回-1System.out.println(b.indexOf("b",5));//7   indexOf(str, index)  如果字符串从index之后含有这个字符或字符串返回第一个所在的位置,如果没有返回-1System.out.println(b.lastIndexOf("b1"));//7     lastIndexOf()字符串包含某字符和字符串的最后一个所在的位置,如果没有返回-1System.out.println(b.lastIndexOf("b",5));//0    lastIndexOf(str,index)字符串在index自强包含某字符和字符串的最后一个所在的位置,如果没有返回-1System.out.println(b.startsWith("a"));//false     startsWith() 判断字符串是否以某字符串开头,如果是就返回true ,反之返回falseSystem.out.println(b.startsWith("a",6));//true     startsWith(str.index) 判断字符串在index处是否以某字符串开头,如果是就返回true ,反之返回falseSystem.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 blocke.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 StringBufferextends AbstractStringBuilderimplements java.io.Serializable, CharSequence* @author      Michael McCloskey* @see         java.lang.StringBuffer* @see         java.lang.String* @since       1.5*/
public final class StringBuilderextends AbstractStringBuilderimplements 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的字符数组

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

   @Overridepublic 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 codeif (minimumCapacity - value.length > 0) {//minimumCapacity 为字符数组中包含字符个数加上要添加的字符串的长度。    然后判断minimumCapacity是否大于存储字符的字符数组长度。value = Arrays.copyOf(value,newCapacity(minimumCapacity));}}

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

    private int newCapacity(int minCapacity) {// overflow-conscious codeint newCapacity = (value.length << 1) + 2;//新的数组长度是原来数组长度的2倍+2if (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()   可以添加字符串以及数字浮点类型,返回添加后的StringBufferSystem.out.println(strbuf);//abc12System.out.println(strbuf.insert(3,"A"));//abcA12   insert(offset,obk)   可以添加数据在offset处,返回添加后的StringBufferchar[] str= {'H','M','G'};System.out.println(strbuf.insert(0, str, 1, 2));//MGabcA12   insert(index, str, offset, len)  插入一个char数组,从char数组第offset开始,长度为len。返回添加后的StringBufferSystem.out.println(strbuf.deleteCharAt(2));  //MGbcA12  deleteCharAt(index) 删除index出的字符,返回stringbufferSystem.out.println(strbuf);// MGbcA12System.out.println(strbuf.delete(2, 70));// MG   delete(start, end) 删除这个范围内的字符,如果end大于stringbuffer的长度,不会报错在源码中会变成stringbuffer的长度,返回stringbufferSystem.out.println(strbuf);// MG  strbuf= new StringBuffer("abcdab");//重新实例化一个对象System.out.println(strbuf.indexOf("b"));//1  indexOf(str)  字符或字符串在stringbuffer中的第一位置,如果没有返回-1System.out.println(strbuf.indexOf("b",3));//5   indexOf(str,index)   字符或字符串在stringbuffer中从index开始的第一位置,如果没有返回-1System.out.println(strbuf.lastIndexOf("b"));//5   lastIndexOf(str)  字符或字符串在stringbuffer中的最后的位置,如果没有返回-1System.out.println(strbuf.lastIndexOf("b", 1));//1   lastIndexOf(str,index) 字符或字符串在stringbuffer中的从开始到index中最后的位置(包含index),如果没有返回-1System.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);//ABCDcbastrbuf.setCharAt(0, 'G');//setCharAt (index,chr)  chr替换了stringbuffer中index位置的字符System.out.println(strbuf);// GBCDcbaSystem.out.println(strbuf.substring(3));//Dcba  substring(index) 截取stringbuffer中字符从index开始,然后返回截出的字符串System.out.println(strbuf);// GBCDcbaSystem.out.println(strbuf.substring(2, 4));//CD  substring(star,end) 截取stringbuffer中字符从star开始到end,然后返回截出的字符串}}

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

 //stringbuffer   中的方法@Overridepublic synchronized StringBuffer append(int i) {toStringCache = null; //super.append(i);return this;}//stringbuilder 中的方法@Overridepublic 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 。

这篇关于java基础 浅析string,StringBuffer与StringBuilder的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1073132

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]