Java基础之String、StringBuffer、StringBuilder浅析

一、前言:

  位于java.lang包下的String、StringBuilder、StringBuffer一般都是用来操作字符串的,这三个类都被final关键字修饰,不能被继承

  相关的API我们可以在这里查看:https://docs.oracle.com/javase/8/docs/api/index.html

  首先说明:

  • String:字符串常量
  • StringBuffer:字符串变量(线程安全的)
  • StringBuilder:字符串变量(非线程安全的)

二、Java中的String

  在这三个类中String类应该算是我们最熟悉的了吧

  String的对象是不可变得。查看jdk文档你就会发现,String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动

2.1 java里 String 类的 本质

  String类的书面解释在本文前言已经提到过了, 是1个用于字符串的类.

  但是这个解释并没有指明String类的本质.

  我们知道, Java类的本质大致上可以理解为 成员(属性) 和 方法的集合体.

  String类也一样, 只不过String类有1个关键的成员, 这个成员保存着数据区的某个字符串的内存地址. 可以理解为1个指针.

  而String类的方法是一些对对应字符串的查询方法(例如indexOf(), charAt()等). 注意, 并没有对这个字符串进行修改的方法哦, 字符串是常量, 不能修改.

  虽然String类不能修改字符串, 但是上面保存字符串地址的成员却是可以被改变的, 也就是说String类的对象可以指向另1个字符串.

  见上图, java的String类实例化1个对象后, 会在堆区划分一块对象的内存, 其中1个关键成员存放的是数据区字符串的地址.

而下面若干个方法内存, 存放的是该函数(方法)在代码区的2进制代码的地址.

2.2 String类实例化对象的第一个方法. new String("abc")

  当然, String类的构造函数有很多个(参数不同), 但是在coding中,常用的实例化对象方法无非是两种.

  第一种就是与其他类一样, 利用构造方法:

  String s = new String("abc");  

  上面的代码做了下面若干个事情.

  • 在数据区中划分一块内存存放字符串, 值是"abc", 这块内存一旦创建, 值"abc" 不能被修改.
  • 在堆区划分1块对象内存, 其中小块用于存放上面字符串的地址, 另一些用于存放函数指针.
  • 在栈区划分一块内存, 存放上面堆区的头部地址.

  下面是1个例子:

  1. package String_kng;
  2. public class String_4{
  3. public static void f(){
  4. String s = new String("cat");
  5. String s2 = new String("cat");
  6. System.out.printf("s: %s\n", s);
  7. System.out.printf("s2: %s\n", s2);
  8. System.out.println(s == s2);
  9. System.out.println(s.equals(s2));
  10. }
  11. }

  上面利用new 实例化了两个对象s和s2 , 它们所指向的字符串值都是"cat"

  然后用 "==" 和 equals来比较两者

  输出:

  1. [java] s: cat
  2. [java] s2: cat
  3. [java] false
  4. [java] true

  可见用equals 来比较s 和 s2, 它们是相等的, 因为它们的内容相同. 而且equals方法在String类里重写过了.

  而用 "==" 比较的是两个对象s 和 s2所指向的地址, 它们所指向的地址是不同的.

  如下图:

  亦即两个new语句分别在数据区和堆区各自都划分2个内存.

  数据区中有两个字符串内存, 它们的值是一样的都是"cat".

  堆区有两个对象内存, 它们分别保存了各自对应的字符串地址.

  而stuck区中两个s1 s2 保存了各自的堆区内存地址.  这两个地址明显是不同的. 也就是 s == s2 返回false的原因.

2.3 String类实例化对象的另一个方法.  = "abc"

  事实上, 我们在编程中新建1个字符串更多情况下会用如下的方式:

  1. String s = "abc";

  这种方式更上面那种有什么区别呢?

  1. package String_kng;
  2. public class String_5{
  3. public static String g(){
  4. String s4 = "cat";
  5. return s4;
  6. }
  7. public static void f(){
  8. String s = new String("cat");
  9. String s2 = "cat";
  10. String s3 = "cat";
  11. System.out.printf("s: %s\n", s);
  12. System.out.printf("s2: %s\n", s2);
  13. System.out.printf("s3: %s\n", s3);
  14. System.out.println(s == s2);
  15. System.out.println(s2 == s3);
  16. System.out.println(s2 == g());
  17. }
  18. }

  这个例子步骤也不复杂:

  首先f()方法里 利用第一种方法实例化了1个值为"cat"的对象s

  然后里利用第二种方法 又 创建了两个String 对象s2 和 s3, 它们的值都是"cat".

  然后用"==" 来比较它们.

  然后f()方法调用g()方法, g()方法利用第二方式实例化了1个值为"cat"的String 对象s4
  最后用 "==" 比较s2 和 s4 的地址.

  输出:

  1. [java] s: cat
  2. [java] s2: cat
  3. [java] s3: cat
  4. [java] false
  5. [java] true
  6. [java] true

  由结果得知, s4 和 s2 和 s3的地址是相同的! 而由第一种方法创建的s 跟前面三者地址不同.

  我们来分析一下,为什么会出现这样的结果:

  首先我们要明白的是

  String str = "abc";

  这样的代码,可能会创建一个对象或者不会创建对象:这里会出现一个名词“字符串实例池

  • 实例池中存在字符串:

  这个名词很形象,在这个字符串实例池中,存放着很多字符串,可能包含有字符串:"abc",所以

  在这种情况下面,上面的语句是不会创建对象的,而是直接引用池子中的字符串:"abc";

  • 实例池中不存在字符串:

  如果字符串"abc"在实例池中并不存在,那么这时,就会初始化一个字符串:"abc",即创建

  一个字符串对象:"abc",并且会把创建好的字符串放入到"字符串实例池"中。

  String str = new String("abc");

  对于关键字:new ,即会产生新的对象,也就是说,每次都会产生新的字符串对象

  所以结论如下:

  利用 = "cat" 方式创建1个String对象时, java 首先会检测当前进程的数据区是否有1个以相同方式创建的值是一样的字符串存在.

  如果无, 则类似 new Sring("cat")方式, 在数据区和堆区都各自划分一块新内存, 用于该创建的对象.

  如果有, 则直接把该对象的地址指向 已存在的堆区内存地址.

  也就是讲, 在f() 里的String s2 = "cat" 相当于执行了 String s2 = new String("cat");

  而在f()里的 String   s3 = "cat" 相当执行了String s3 = s2;

  而在g()里, 理论上g()是不能访问f()里的 局部变量的, 但是g()还是检测到数据区存在用相同方式创建而且值1个样的字符串.

  所以s4 也指向了堆区的那一块内存.

  如下图:

  这个例子说明了, 在同1个java程序中, 所有用 " = "abc" " 方式创建的而且具有相同值的多个String对象其实都是同1个对象. 因为它们指向同一块堆区的内存.

  由于这种特性, 所以这种用" = "abc"" 方式创建的对象十分适合做 synchronized对象锁 要锁的对象.   不用担心锁的是两个不同的对象导致 多线程同步失败.

2.4.String类的常用方法.

  1.public char charAt(int index) //返回字符串中第index个字符

  2.public int length()  //返回字符串的长度

  3.public int indexOf(String str)//返回字符串中出现str的第1个位置

  4.public int indexOf(String str, int fromIndex)//返回字符串中, 从第fromIndex个字符数起, 出现str的第1个位置, 这个方法是上面方法的重载

  5.public boolean equalsIgnoreCase(String str)//忽略大小写, 比较两个字符是否相等.

  6.public String replace(char oldChar, char newChar)//返回1个新字符串, 该新字符串内的oldChar被newChar替换掉, 注意旧字符串没有被修改.

  7.public boolean startsWith(String prefix)//判断字符串是否以 prefix 开头

  8.public boolean endsWith(String suffix)//判断字符产是否以suffix 结尾

  9.public String subString(int beginIndex)//截取从第beginIndex个字符开始到最后1个字符, 返回1个新字符串

  10.public String subString(int beginIndex, int endIndex)//截取从第beginIndex个字符开始, 第endIndex个字符, 返回1个新字符串, 是上面方法的重载

  11.public static String valueOf(...)//注意这个是静态方法. 可以把其他基本数据类型转换成String对象

  12.Integer.parseInt(String s)//这个是另1个类Integer 的方法, 可以把字符串转换成int类型.  会抛出异常..

三、Java中的StringBuffer

  StringBuffer类和String一样,也用来代表字符串,只是由于StringBuffer的内部实现方式和String不同,所以StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。

  所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。

  在StringBuffer类中存在很多和String类一样的方法,这些方法在功能上和String类中的功能是完全一样的。

  但是有一个最显著的区别在于,对于StringBuffer对象的每次修改都会改变对象自身,这点是和String类最大的区别。

  另外由于StringBuffer是线程安全的,关于线程的概念后续有专门的章节进行介绍,所以在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。

  下面谈谈StringBuffer的使用:

  • StringBuffer对象的初始化

  StringBuffer对象的初始化不像String类的初始化一样,Java提供的有特殊的语法,而通常情况下一般使用构造方法进行初始化。

  例如: StringBuffer s = new StringBuffer();

  这样初始化出的StringBuffer对象是一个空的对象。

  如果需要创建带有内容的StringBuffer对象,则可以使用:

StringBuffer s = new StringBuffer(“abc”);

  这样初始化出的StringBuffer对象的内容就是字符串”abc”。

  需要注意的是,StringBuffer和String属于不同的类型,也不能直接进行强制类型转换,下面的代码都是错误的:

  StringBuffer s = “abc”; //赋值类型不匹配

  StringBuffer s = (StringBuffer)”abc”;    //不存在继承关系,无法进行强转

  StringBuffer对象和String对象之间的互转的代码如下:

  String s = “abc”;

  StringBuffer sb1 = new StringBuffer(“123”);

  StringBuffer sb2 = new StringBuffer(s);   //String转换为StringBuffer

  String s1 = sb1.toString();  //StringBuffer转换为String

  • StringBuffer的常用方法

  StringBuffer类中的方法主要偏重于对于字符串的变化,例如追加、插入和删除等,这个也是StringBuffer和String类的主要区别。

  ①append方法

  public StringBuffer append(boolean b)

  该方法的作用是追加内容到当前StringBuffer对象的末尾,类似于字符串的连接。调用该方法以后,StringBuffer对象的内容也发生改变,  例如:

    StringBuffer sb = new StringBuffer(“abc”);

    sb.append(true);

  则对象sb的值将变成”abctrue”。

  使用该方法进行字符串的连接,将比String更加节约内容,例如应用于数据库SQL语句的连接,例如:

    StringBuffer sb = new StringBuffer();

    String user = “test”;

    String pwd = “123”;

    sb.append(“select * from userInfo where username=“).append(user).append(“ and pwd=”).append(pwd);

    这样对象sb的值就是字符串“select * from userInfo where username=test and pwd=123”。

  ②deleteCharAt方法

  public StringBuffer deleteCharAt(int index)

  该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串。例如:

  StringBuffer sb = new StringBuffer(“Test”);

  sb. deleteCharAt(1);

  该代码的作用删除字符串对象sb中索引值为1的字符,也就是删除第二个字符,剩余的内容组成一个新的字符串。所以对象sb的值变为”Tst”。

  还存在一个功能类似的delete方法:

    public StringBuffer delete(int start,int end)

    该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引值的区间。例如:

      StringBuffer sb = new StringBuffer(“TestString”);

      sb. delete (1,4);

      该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符,剩余的字符形成新的字符串。则对象sb的值是”TString”。

  ③insert方法

  public StringBuffer insert(int offset, boolean b)

  该方法的作用是在StringBuffer对象中插入内容,然后形成新的字符串。

  例如:

    StringBuffer sb = new StringBuffer(“TestString”);

    sb.insert(4,false);

    该示例代码的作用是在对象sb的索引值4的位置插入false值,形成新的字符串,则执行以后对象sb的值是”TestfalseString”。

  ④reverse方法

  public StringBuffer reverse()

  该方法的作用是将StringBuffer对象中的内容反转,然后形成新的字符串。例如:

    StringBuffer sb = new StringBuffer(“abc”);

    sb.reverse();

  经过反转以后,对象sb中的内容将变为”cba”。

  ⑤setCharAt方法

  public void setCharAt(int index, char ch)

  该方法的作用是修改对象中索引值为index位置的字符为新的字符ch。例如:

    StringBuffer sb = new StringBuffer(“abc”);

    sb.setCharAt(1,’D’);

    则对象sb的值将变成”aDc”。

  ⑥trimToSize方法

  public void trimToSize()

  该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费。

  总之,在实际使用时,String和StringBuffer各有优势和不足,可以根据具体的使用环境,选择对应的类型进行使用

四、Java中的StringBuilder

  一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。 在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符添加或插入到字符串生成器中。append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。

  StringBuilder中的常用方法:

  (1)Append 方法可用来将文本或对象的字符串表示形式添加到由当前 StringBuilder对象表示的字符串的结尾处。以下示例将一个 StringBuilder对象初始化为“Hello World”,然后将一些文本追加到该对象的结尾处。将根据需要自动分配空间。

  StringBuilderMyStringBuilder = new StringBuilder("Hello World!");

  MyStringBuilder.Append(" What a beautiful day."); Console.WriteLine(MyStringBuilder);

  此示例将 Hello World! What abeautiful day.显示到控制台。

  (2)AppendFormat 方法将文本添加到 StringBuilder的结尾处,而且实现了 IFormattable接口,因此可接受格式化部分中描述的标准格式字符串。可以使用此方法来自定义变量的格式并将这些值追加到 StringBuilder的后面。以下示例使用 AppendFormat方法将一个设置为货币值格式的整数值放置到 StringBuilder的结尾。

  int MyInt= 25;

  StringBuilder MyStringBuilder = new StringBuilder("Your total is ");

  MyStringBuilder.AppendFormat("{0:C} ", MyInt);

  Console.WriteLine(MyStringBuilder);

  此示例将 Your total is $25.00显示到控制台。

  (3)Insert 方法将字符串或对象添加到当前 StringBuilder中的指定位置。以下示例使用此方法将一个单词插入到 StringBuilder的第六个位置。

  StringBuilderMyStringBuilder = new StringBuilder("Hello World!");

  MyStringBuilder.Insert(6,"Beautiful ");

  Console.WriteLine(MyStringBuilder);

  此示例将 Hello BeautifulWorld!显示到控制台。

  (4)可以使用 Remove方法从当前 StringBuilder中移除指定数量的字符,移除过程从指定的从零开始的索引处开始。以下示例使用 Remove方法缩短 StringBuilder。

  StringBuilderMyStringBuilder = new StringBuilder("Hello World!");

  MyStringBuilder.Remove(5,7);

  Console.WriteLine(MyStringBuilder);

  此示例将 Hello显示到控制台。

  (5)使用 Replace方法,可以用另一个指定的字符来替换 StringBuilder对象内的字符。以下示例使用 Replace方法来搜索 StringBuilder对象,查找所有的感叹号字符 (!),并用问号字符 (?)来替换它们。

  StringBuilderMyStringBuilder = new StringBuilder("Hello World!");

  MyStringBuilder.Replace('!', '?');

  Console.WriteLine(MyStringBuilder);

五、三者的区别:

  这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。

  • 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String

  String最慢的原因:

  String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:

  1 String str="abc";

  2 System.out.println(str);

  3 str=str+"de";

  4 System.out.println(str);

  如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

  而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

  另外,有时候我们会这样对字符串进行赋值

  1.String str="abc"+"de";

  2 StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");

  3 System.out.println(str);

  4 System.out.println(stringBuilder.toString());

  这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和

  String str="abcde";

  是完全一样的,所以会很快,而如果写成下面这种形式

  1 String str1="abc";

  2 String str2="de";

  3 String str=str1+str2;

  那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。

  • 再来说线程安全

  在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

  如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

  • 总结一下

  String:适用于少量的字符串操作的情况

  StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

  StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

参考文章:

  http://www.360doc.com/content/18/0120/12/52205844_723589162.shtml

  https://www.cnblogs.com/springcsc/archive/2009/12/03/1616330.html

  http://blog.csdn.net/l_kanglin/article/details/53291301

  https://www.cnblogs.com/su-feng/p/6659064.html

  https://www.cnblogs.com/hongten/p/hongten_string_qubie.html

Java基础之String、StringBuffer、StringBuilder浅析的更多相关文章

  1. 【Java基础】String StringBuffer StringBuilder

    String String是不可变的 我们都知道String不是基本数据类型,而是一个对象,并且是final类型的,不可变的.(public final class String) 查看以下代码: S ...

  2. Java基础知识 String StringBuffer StringBuilder三者的区别(面试题)

    相同点:String.StringBuffer.StringBuilder最终底层存储与操作的都是char数组,StringBuffer和StringBuilder都继承了AbstractString ...

  3. java 基础 5 String StringBuffer StringBuilder

    String是不可变的,原因 1是可以缓存hash值,因为String的hash值经常被使用,例如String用作HashMap等.不可变特性  使得hash值不变,因此只需要进行一次计算: 2Str ...

  4. Java学习笔记--String StringBuffer StringBuilder

    String StringBuffer StringBuilder String http://docs.oracle.com/javase/7/docs/api/ 中文: http://www.cn ...

  5. 浅谈 Java 字符串(String, StringBuffer, StringBuilder)

    我们先要记住三者的特征: String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全) 一.定义 查看 API 会发现,String ...

  6. 重温java中的String,StringBuffer,StringBuilder类

    不论什么一个系统在开发的过程中, 相信都不会缺少对字符串的处理. 在 java 语言中, 用来处理字符串的的类经常使用的有 3 个: String.StringBuffer.StringBuilder ...

  7. JAVA基础之——String、StringBuilder、StringBuffer区别和使用场景

    本文主要讲解String.StringBuilder.StringBuffer区别和应用场景 本文以jdk1.8为例 1 String 操作过程:每次赋值时新建一个String对象. 2 String ...

  8. Android/Java 中的 String, StringBuffer, StringBuilder的区别和使用

    Android 中的 String, StringBuffer 和 StringBuilder 是移动手机开发中经常使用到的字符串类.做为基础知识是必须要理解的,这里做一些总结. A.区别 可以从以下 ...

  9. 在JAVA中,String,Stringbuffer,StringBuilder 的区别

    首先是,String,StringBuffer的区别 两者的主要却别有两方面,第一是线程安全方面,第二是效率方面 线程安全方面: String  不是线程安全的,这意味着在不同线程共享一个String ...

  10. Java基础(32):String与StringBuilder、StringBuffer的区别(String类)

    在Java中,除了可以使用 String 类来存储字符串,还可以使用 StringBuilder 类或 StringBuffer 类存储字符串,那么它们之间有什么区别呢? String 类具有是不可变 ...

随机推荐

  1. 十、SQL中EXISTS的用法 十三、sql server not exists

    十.SQL中EXISTS的用法 EXISTS用于检查子查询是否至少会返回一行数据,该子查询实际上并不返回任何数据,而是返回值True或False EXISTS 指定一个子查询,检测 行 的存在. 语法 ...

  2. IntegerCache的妙用和陷阱

    转载自IntegerCache的妙用和陷阱 考虑下面的小程序,你认为会输出为什么结果? public class Test {     public static void main(String[] ...

  3. T-SQL 小全

    --====================================================== ----数据库概念:创建.删除.使用数据库 ----================= ...

  4. 在WPF中嵌入WebBrowser可视化页面

    无论是哪种C/S技术,涉及数据可视化就非常的累赘了,当然大神也一定有,只不过面向大多数人,还是通过网页来实现,有的时候不想把这两个功能分开,一般会是客户的原因,所以我们打算在WPF中嵌入WebBrow ...

  5. STL 大法好

    #include <vector>  1.支持随机访问,但不支持在任意位置O(1)插入:    2.定义:  ```cpp      vector<int> a;  ```  ...

  6. 机器学习经典分类算法 —— k-均值算法(附python实现代码及数据集)

    目录 工作原理 python实现 算法实战 对mnist数据集进行聚类 小结 附录 工作原理 聚类是一种无监督的学习,它将相似的对象归到同一个簇中.类似于全自动分类(自动的意思是连类别都是自动构建的) ...

  7. spark 源码分析之二十二-- Task的内存管理

    问题的提出 本篇文章将回答如下问题: 1.  spark任务在执行的时候,其内存是如何管理的? 2. 堆内内存的寻址是如何设计的?是如何避免由于JVM的GC的存在引起的内存地址变化的?其内部的内存缓存 ...

  8. 【JDK】JDK源码分析-Collection

    Java 集合框架(Java Collections Framework, JCF)包含很多平时开发中的常用类,例如 List.Set.ArrayList.HashMap.HashSet 等,因此打算 ...

  9. java并发程序和共享对象实用策略

    java并发程序和共享对象实用策略 在并发程序中使用和共享对象时,可以使用一些实用的策略,包括: 线程封闭 只读共享.共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它.共享的只读对象包括不 ...

  10. 8、JAVA中的用户输入(I/0交互过程)

    这里在数组的学习中用到了用户输入,也就是交互模式,日常的数据,不可能每一次都是程序员定义好的,终究需要用户与程序之间进行交互,机器等待用户输入,用户通过键盘输入所需数据(数据包括,字符,字串,数值等) ...