Java学习——String,StringBuffer和StringBuilder

时间:2022-09-20 17:09:04

Java学习——String,StringBuffer和StringBuilder

摘要:本文主要介绍了String字符串在内存中的存储情况,以及StringBuffer和StringBuilder的区别。

部分内容来自以下博客:

https://www.cnblogs.com/xrq730/p/4841518.html

https://www.cnblogs.com/xiaoxi/p/6036701.html

https://blog.csdn.net/k393393/article/details/79078670

String类

不可变性

String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。

String类其实是通过char数组来保存字符串的。

所有对String类的操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。

String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何改变都会生成新的对象。

下面代码中对String的改动并不会影响到原有的String字符串:

 public static void change(String str) {
str = str + "def";
} public static void main(String[] args) {
String a = "abc";
String b = new String("abc");
change(a);
change(b);
System.out.println("a >>> " + a);
System.out.println("b >>> " + b);
}

运行结果:

 a >>> abc
b >>> abc

字符串常量池

我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。

每当我们创建字符串常量时,JVM会首先检查字符串常量池。如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,同时返回对该实例的引用。

由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。

来看两行代码:

 String a = "abc";
String b = new String("abc");

第一行创建的字符串常量a保存的是指向常量池里abc的地址,这是在编译时就能确定的。

第二个创建的字符串常量b保存的是指向堆内存里abc的地址,而因为字符串都是保存在常量池里的,所以堆内存里保存的也是一个指向常量池的abc的地址。因为在编译时是不确定的,所以b保存的地址值是在运行时确定的。

例子1:

 public void test1() {
String a = "abc";
String b = "abc";
String c = new String("abc");
String d = new String("abc");
System.out.println(a == b);// true
System.out.println(a == c);// false
System.out.println(c == d);// false
}

分析:

a和b保存的都是指向常量池里“abc”的引用地址,在编译期间就已经确定了,所以a和b判断返回true。

用new创建的字符串不是常量,不能在编译期就确定,所以字符串不放入常量池中,它们有自己的地址空间。c和d保存的是两个堆内存的引用地址,是两个不同的地址空间,所以a和c、c和d判断都是false。

例子2:

 public void test2() {
String a = "abc";
String b = "def";
String c = a + b;
String d = "abc" + "def";
String e = "abc" + new String("def");
String f = "abcdef";
System.out.println(c == d);// false
System.out.println(c == f);// false
System.out.println(d == f);// true
System.out.println(e == f);// false
}

分析:

因为a、b、d、f保存的都是指向常量池的引用,它们在编译期就被确定了,所以d和f判断返回true。

因为d和f均是在运行时才确定的,保存的都是堆内存中的引用地址,所以c和d、c和f、e和f判断返回都是false。

例子3:

 public void test3() {
final String a = "abc";
String b = "def";
String c = "abcdef";
String d = a + b;
String e = a + "def";
System.out.println(c == d);// false
System.out.println(c == e);// true
}

分析:

因为a被final修饰了,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中,所以e的操作在编译期间就可以确定了,所以c和e判断为true。

因为b没有被final修饰,所以d还是不能在编译期间确定,所以d保存的还是堆内存里的地址,所以c和d判断返回false。

字符串的拼接操作

编译器每次碰到“+”的时候,会new一个StringBuilder出来,接着调用append()方法,再调用toString()方法,生成新字符串。那么,这意味着,如果代码中有很多的“+”,就会每个“+”生成一次StringBuilder,这种方式对内存是一种浪费,效率很不好。

字符串对常量的拼接操作和对字面量的拼接是有区别的。

 public void test() {
String a = "abc";
String b = "def";
String c = "abcdef";
String d = "abc" + "def";
String e = a + b;
System.out.println(c == d);// true
System.out.println(c == e);// false
}

字符串字面量拼接操作是在Java编译器编译期间就执行了,也就是说编译器编译时,直接把“abc”和“def”进行拼接,得到“abcdef”常量,并且直接将这个常量放入字符串池中。

这样做实际上是一种优化,将3个字面量合成一个,避免了创建多余的字符串对象。而字符串引用的拼接运算是在Java运行期间执行的,它会在堆内存中重新创建一个拼接后的字符串对象。

对于直接相加字符串,效率很高,因为在编译器便确定了它的值。对于间接相加(即包含字符串引用),效率要比直接相加低,因为在编译器不会对引用变量进行优化。

关于String.intern()

intern()方法使用:一个初始为空的字符串池,它由类String独自维护。当调用 intern()方法时,如果池已经包含一个等于此String对象的字符串(用equals()方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。

它遵循以下规则:对于任意两个字符串a和b,当且仅当a.equals(b)为true时,a.intern()==b.intern()才为true。

存在于class文件中的常量池,在运行期间被JVM装载,并且可以扩充,String的intern()方法就是扩充常量池的一个方法。当一个String实例str调用intern()方法时,查找常量池中是否有相同unicode的字符串常量。如果有,则返回其引用。如果没有,则在常量池中增加一个unicode等于str的字符串并返回它的引用。

 public void test(){
String a = "abc";
String b = new String("abc");
String c = new String("abc");
System.out.println(a == b);// false
b.intern();
c = c.intern();
System.out.println(a == b);// flase
System.out.println(a == b.intern());// true
System.out.println(a == c);// true
}

StringBuilder

构造方法

public StringBuilder():创建一个长度为默认16的char类型的数组。

public StringBuilder(int capacity):创建一个指定长度的char类型的数组。

常用方法

public StringBuilder append(String str):追加内容。

public StringBuilder delete(int start, int end):删除指定位置的内容。

public StringBuilder replace(int start, int end, String str):把指定位置的字符串替换为传入的字符串。

public StringBuilder insert(int offset, String str):在指定位置插入传入的字符串。

StringBuffer

构造方法

public StringBuffer():创建一个长度为默认16的char类型的数组。

public StringBuffer(int capacity):创建一个指定长度的char类型的数组。

常用方法

public synchronized StringBuffer append(String str):追加内容。

public synchronized StringBuffer delete(int start, int end):删除指定位置的内容。

public synchronized StringBuffer replace(int start, int end, String str):把指定位置的字符串替换为传入的字符串。

public synchronized StringBuffer insert(int offset, String str):在指定位置插入传入的字符串。

String、StringBuffer、StringBuilder三者的区别

可变与不可变

String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。

是否多线程安全

String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer与StringBuilder中的方法和功能完全是等价的,只是StringBuffer中的方法大都采用了synchronized关键字进行修饰,因此是线程安全的,而StringBuilder没有这个修饰,可以被认为是非线程安全的。

执行效率

StringBuilder > StringBuffer > String

当然这个是相对的,不一定在所有情况下都是这样。比如 String str = "hello" + "world"; 的效率就比 StringBuilder builder = new StringBuilder().append("hello").append("world"); 要高。

因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:

当字符串相加操作或者改动较少的情况下,建议使用String这种形式;

当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。

Java学习——String,StringBuffer和StringBuilder的更多相关文章

  1. Java学习|String,StringBuffer,StringBuilder?

    1 String   (1) String的创建机理 由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池.其运行机制是:创建一个字 ...

  2. Java中String,StringBuffer与StringBuilder的差别

    String 字符串常量: StringBuffer 字符串变量〈缓冲区〉(线程安全): StringBuilder 字符串变量〈缓冲区〉(非线程安全): 简要的说, String 类型和 Strin ...

  3. 【Java】String,StringBuffer与StringBuilder的区别??

    String 字符串常量StringBuffer 字符串变量(线程安全)StringBuilder 字符串变量(非线程安全) 简要的说, String 类型和 StringBuffer 类型的主要性能 ...

  4. java中String,StringBuffer与StringBuilder的区别??

    本文着重介绍下,应该在何时恰当的使用string,stringbuffer,stringbuilder. 1,执行速度 StringBuilder >  StringBuffer  >  ...

  5. Java中string拼接,StringBuilder,StringBuffer和+

    Java中string拼接,StringBuilder,StringBuffer和+,到底哪个更合适? StringBuilder线程不安全,效率较线程安全的StringBuffer高.jdk1.5之 ...

  6. Java String, StringBuffer和StringBuilder实例

    1- 分层继承2- 可变和不可变的概念3- String3.1- 字符串是一个非常特殊的类3.2- String 字面值 vs. String对象3.3- String的方法3.3.1- length ...

  7. String,StringBuffer和StringBuilder源码解析[基于JDK6]

    最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下 ...

  8. String,StringBuffer和StringBuilder的异同

                                                                    String,StringBuffer和StringBuilder的异同 ...

  9. String,StringBuffer与StringBuilder

    1. String,StringBuffer与StringBuilder的区别 String:存储在常量池中:是不可变的字符序列,任何对String值的改变都会引发新的String对象的生成,因此执行 ...

随机推荐

  1. 11、网页制作Dreamweaver(补充:JS零碎知识点&&正则表达式)

    JS知识点 回车符/r和换行符/n的区别:/r 相当于enter,是段落与段落之间的区别, /n 相当于shift+enter,是行与行之间距离,比较小 几种window操作方法: 1.获取当前窗口大 ...

  2. aspx页面与服务器控件间运行原理

    一.进入服务器控件以及aspx页面前,必须的准备: a)在WebForm 中只要带有runat="server" 那么那就是一个控件. b)如果form有runat="s ...

  3. VLAN是什么

    VLAN是什么?VLAN,是英文Virtual Local Area Network的缩写,中文名为"虚拟局域网", VLAN是一种将局域网(LAN)设备从逻辑上划分(注意,不是从 ...

  4. 读取word文件.选择了TextParse

    待续! 代码还没分离出来.. 分离后会上传上来 不支持wps 文件 . ]]>

  5. jquery解决onmouseover和onmouseout合用的bug问题

    经常会遇到鼠标放到一个元素上显示另外一个元素,这两个元素是父子关系,比如在A上绑定mouseover和mouseout事件来显示或隐藏B元素,A元素包含B元素,当鼠标移到B元素后浏览器认为你移开了A, ...

  6. JS 实现图片的预加载(转载)

    图片预加载是web开发中一种应用相当广泛的技术,比如我们在做图片翻转显示等特效的时候,为了让图片在转换的时候不出现等待,我们最好是先让图片下载到本地,然后在继续执行后续的操作. 下面的函数实现了一个我 ...

  7. 背水一战 Windows 10 (91) - 文件系统: Application Data 中的文件操作, Application Data 中的“设置”操作, 通过 uri 引用 Application Data 中的媒体

    [源码下载] 背水一战 Windows 10 (91) - 文件系统: Application Data 中的文件操作, Application Data 中的“设置”操作, 通过 uri 引用 Ap ...

  8. MongoDB 设置账号和密码

    一.安装MongoDB 1.环境配置: i.操作系统:CentOS release 6.8 (Final) [root@iZ2ze2pbbffhmn53ao4tuaZ bin]# cat /etc/r ...

  9. Ybquery项目部署idea

    家庭公计网的引用  

  10. fastDFS 分布式文件系统应用

    环境准备 使用的系统软件 名称 说明 centos 7.x libfatscommon FastDFS分离出的一些公用函数包 FastDFS FastDFS本体 fastdfs-nginx-modul ...