Java 从底层与接口实现了解String、StringBuffer、StringBuilder
yuyutoo 2025-01-10 18:14 3 浏览 0 评论
String、StringBuffer 和 StringBuilder的接口实现关系:
String:字符串常量,字符串长度不可变。Java中String 是immutable(不可变)的。用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。
StringBuffer:宣称线程安全的字符串变量(Synchronized,即线程安全,multiple threads cannot access it simultaneously,可将字符串缓冲区安全地用于多个线程)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用 StringBuffer,如果想转成 String 类型,可以调用 StringBuffer 的 toString() 方法。
StringBuilder:字符串变量(非线程安全,但效率更高)。在内部 StringBuilder 对象被当作是一个包含字符序列的变长数组。
三类字符串使用建议:
如果要操作少量的数据用 String ;
单线程操作大量数据用StringBuilder ;
多线程操作大量数据,用StringBuffer。
1 关于 String 为啥是不可改变的
字符串实际上就是一个 char 数组,并且内部就是封装了一个 char 数组。
并且这里 char 数组是被 final 修饰的:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
并且 String 中的所有的方法,都是对于 char 数组的改变,只要是对它的改变,方法内部都是返回一个新的 String 实例。
对于字符串的加运算,当编译成 class 文件时,会自动编译为 StringBuffer 来进行字符串的连接操作。
同时对于字符串常量池:
当一个字符串是一个字面量时,它会被放到一个常量池中,等待复用。
String 类的常见面试问题:
面试题一:
String s1 = "abc"; // 常量池
String s2 = new String("abc"); // 堆内存中
System.out.println(s1==s2); // false,两个对象的地址值不一样。
System.out.println(s1.equals(s2)); // true
面试题二:
String s1="a"+"b"+"c";
String s2="abc";
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
// java 中常量优化机制,编译时 s1 已经成为 abc 在常量池中查找创建,s2 不需要再创建。
面试题三:
String s1="ab";
String s2="abc";
String s3=s1+"c";
System.out.println(s3==s2); // false
System.out.println(s3.equals(s2)); // true
先在常量池中创建 ab ,地址指向 s1, 再创建 abc ,指向 s2。对于 s3,先创建StringBuilder(或 StringBuffer)对象,通过 append 连接得到 abc ,再调用 toString() 转换得到的地址指向 s3。故 (s3==s2) 为 false。
2 StringBuffer 类
在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象。
Java.lang.StringBuffer 是宣称线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
3 StringBuilder 类
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
关于线程安全,即使你真的遇到了这样的场景,很不幸的是,恐怕你仍然有 99.99....99% 的情况下没有必要选择 stringbuffer,因为 stringbuffer 的线程安全,仅仅是保证 jvm 不抛出异常顺利的往下执行而已,它可不保证逻辑正确和调用顺序正确。大多数时候,我们需要的不仅仅是线程安全,而是锁。
最后,为什么会有 stringbuffer 的存在,如果真的没有价值,为什么 jdk 会提供这个类?答案太简单了,因为最早是没有 stringbuilder 的,sun 的人不知处于何种愚蠢的考虑,决定让 stringbuffer 是线程安全的,然后大约 10 年之后,人们终于意识到这是一个多么愚蠢的决定,意识到在这 10 年之中这个愚蠢的决定为 java 运行速度慢这样的流言贡献了多大的力量,于是,在 jdk1.5 的时候,终于决定提供一个非线程安全的 stringbuffer 实现,并命名为 stringbuilder。顺便,javac 好像大概也是从这个版本开始,把所有用加号连接的 string 运算都隐式的改写成 stringbuilder,也就是说,从 jdk1.5 开始,用加号拼接字符串已经没有任何性能损失了。
"用加号拼接字符串已经没有任何性能损失了"这种说法并不严谨,严格的说,如果没有循环的情况下,单行用加号拼接字符串是没有性能损失的,java 编译器会隐式的替换成 stringbuilder,但在有循环的情况下,编译器没法做到足够智能的替换,仍然会有不必要的性能损耗,因此,用循环拼接字符串的时候,还是老老实实的用 stringbuilder 吧。
4 关于字符串字面量
Like many Java objects, all String instances are created on the heap, even literals. When the JVM finds a String literal that has no equivalent reference in the heap, the JVM creates a corresponding String instance on the heap and it also stores a reference to the newly created String instance in the String pool. Any other references to the same String literal are replaced with the previously created String instance in the heap.
与许多Java对象一样,所有字符串实例都是在堆上创建的,甚至是文字。当JVM发现堆中没有等效引用的字符串文本时,JVM会在堆上创建相应的字符串实例,并在字符串池中存储对新创建的字符串实例的引用。对同一字符串文字的任何其他引用都将替换为堆中先前创建的字符串实例。
class Strings
{
public static void main (String[] args)
{
String a = "alpha";
String b = "alpha";
String c = new String("alpha");
//All three strings are equivalent
System.out.println(a.equals(b) && b.equals(c));
//Although only a and b reference the same heap object
System.out.println(a == b);
System.out.println(a != c);
System.out.println(b != c);
}
}
图示:
When we use double quotes to create a String, it first looks for String with same value in the String pool, if found it just returns the reference else it creates a new String in the pool and then returns the reference.
当我们使用双引号创建字符串时,它首先在字符串池中查找具有相同值的字符串,如果找到,它只返回引用,否则它在池中创建一个新字符串,然后返回引用。
However using new operator, we force String class to create a new String object in heap space. We can use intern() method to put it into the pool or refer to other String object from string pool having same value.
然而,使用new操作符,我们强制String类在堆空间中创建一个新的String对象。我们可以使用intern()方法将其放入池中,或引用字符串池中具有相同值的其他字符串对象。
The String pool itself is also created on the heap. (Version < Java SE 7)
Java SE 7以前的版本的字符串池本身也在堆上创建。
Before Java 7, String literals were stored in the runtime constant pool in the method area of PermGen, that had a fixed size.
在Java 7之前,字符串文本存储在PermGen方法区域的运行时常量池中,该池的大小是固定的。
The String pool also resided in PermGen. (Version ≥ Java SE 7)
Java SE 7 的字符串池也位于PermGen中。
In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String. intern() method will see more significant differences.
在JDK 7中,内部字符串不再分配给Java堆的永久生成(PermGen),而是与应用程序创建的其他对象一起分配给Java堆的主要部分(称为年轻和老年代)。此更改将导致更多数据驻留在主Java堆中,而永久生成中的数据更少,因此可能需要调整堆大小。由于这种变化,大多数应用程序在堆使用方面只会看到相对较小的差异,但较大的应用程序会加载许多类或大量使用字符串。intern()方法将看到更显著的差异。
绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。
其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
在 JDK 1.8 中, HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小。
去除永久代的原因:(1)为了HotSpot与JRockit的融合;(2)永久代大小不容易确定,PermSize指定太小容易造成永久代OOM,与老年代没关系。字符串存在永久代中,容易出现性能问题和内存溢出。
ref
https://www.runoob.com/java/java-string.html
https://www.runoob.com/java/java-stringbuffer.html
https://www.cnblogs.com/lixuwu/p/10746125.html
-End-
相关推荐
- 当 Linux 根分区 (/) 已满时如何释放空间?
-
根分区(/)是Linux文件系统的核心,包含操作系统核心文件、配置文件、日志文件、缓存和用户数据等。当根分区满载时,系统可能出现无法写入新文件、应用程序崩溃甚至无法启动的情况。常见原因包括:...
- 玩转 Linux 之:磁盘分区、挂载知多少?
-
今天来聊聊linux下磁盘分区、挂载的问题,篇幅所限,不会聊的太底层,纯当科普!!1、Linux分区简介1.1主分区vs扩展分区硬盘分区表中最多能存储四个分区,但我们实际使用时一般只分为两...
- Linux 文件搜索神器 find 实战详解,建议收藏
-
在Linux系统使用中,作为一个管理员,我希望能查找系统中所有的大小超过200M文件,查看近7天系统中哪些文件被修改过,找出所有子目录中的可执行文件,这些任务需求...
- Linux 操作系统磁盘操作(linux 磁盘命令)
-
一、文档介绍本文档描述Linux操作系统下多种场景下的磁盘操作情况。二、名词解释...
- Win10新版19603推送:一键清理磁盘空间、首次集成Linux文件管理器
-
继上周四的Build19592后,微软今晨面向快速通道的Insider会员推送Windows10新预览版,操作系统版本号Build19603。除了一些常规修复,本次更新还带了不少新功能,一起来了...
- Android 16允许Linux终端使用手机全部存储空间
-
IT之家4月20日消息,谷歌Pixel手机正朝着成为强大便携式计算设备的目标迈进。2025年3月的更新中,Linux终端应用的推出为这一转变奠定了重要基础。该应用允许兼容的安卓设备...
- Linux 系统管理大容量磁盘(2TB+)操作指南
-
对于容量超过2TB的磁盘,传统MBR分区表的32位寻址机制存在限制(最大支持2.2TB)。需采用GPT(GUIDPartitionTable)分区方案,其支持64位寻址,理论上限为9.4ZB(9....
- Linux 服务器上查看磁盘类型的方法
-
方法1:使用lsblk命令lsblk输出说明:TYPE列显示设备类型,如disk(物理磁盘)、part(分区)、rom(只读存储)等。...
- ESXI7虚机上的Ubuntu Linux 22.04 LVM空间扩容操作记录
-
本人在实际的使用中经常遇到Vmware上安装的Linux虚机的LVM扩容情况,最终实现lv的扩容,大多数情况因为虚机都是有备用或者可停机的情况,一般情况下通过添加一块物理盘再加入vg,然后扩容lv来实...
- 5.4K Star很容易!Windows读取Linux磁盘格式工具
-
[开源日记],分享10k+Star的优质开源项目...
- Linux 文件系统监控:用脚本自动化磁盘空间管理
-
在Linux系统中,文件系统监控是一项非常重要的任务,它可以帮助我们及时发现磁盘空间不足的问题,避免因磁盘满而导致的系统服务不可用。通过编写脚本自动化磁盘空间管理,我们可以更加高效地处理这一问题。下面...
- Linux磁盘管理LVM实战(linux实验磁盘管理)
-
LVM(逻辑卷管理器,LogicalVolumeManager)是一种在Linux系统中用于灵活管理磁盘空间的技术,通过将物理磁盘抽象为逻辑卷,实现动态调整存储容量、跨磁盘扩展等功能。本章节...
- Linux查看文件大小:`ls`和`du`为何结果不同?一文讲透原理!
-
Linux查看文件大小:ls和du为何结果不同?一文讲透原理!在Linux运维中,查看文件大小是日常高频操作。但你是否遇到过以下困惑?...
- 使用 df 命令检查服务器磁盘满了,但用 du 命令发现实际小于磁盘容量
-
在Linux系统中,管理员或开发者经常会遇到一个令人困惑的问题:使用...
- Linux磁盘爆满紧急救援指南:5步清理释放50GB+小白也能轻松搞定
-
“服务器卡死?网站崩溃?当Linux系统弹出‘Nospaceleft’的红色警报,别慌!本文手把手教你从‘删库到跑路’进阶为‘磁盘清理大师’,5个关键步骤+30条救命命令,快速释放磁盘空间,拯救你...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)