Java——String对象
前言
实际上任何语言都没有提供字符串这个概念,而是使用字符数组来描述字符串。Java里面严格来说也是没有字符串的,在所有的开发里面字符串的应用有很多,于是Java为了应对便创建了String类这个字符串类。使用""定义的内容都是字符串,理解Java的String类需要从类的角度和内存关系上分析这个类。
下面将介绍:
- String类对象的两种实例化方式
- 使用"=="和equals比较字符串是否相等
- String常量为匿名对象
- String两种实例化方式的区别
- 字符串一旦定义则不可变
- 字节与字符串
- 字符串中的方法分类
- 重载"+"与StringBuilder
- StringBuffer与StringBuilder
String类对象的两种实例化方式
String name1 = "Sakura"; //直接赋值方式
String name2 = new String("Sakura"); //利用构造方法实例化
使用"=="和equals比较字符串是否相等

使用"=="比较的是两个对象在内存中的地址是否一致,也就是比较两个对象是否为同一个对象。
使用equals()方法比较的是对象的值是否相等,name1和name2所指对象的值都是"Sakura"所以输出为true。
String常量为匿名对象
像"Sakura"这样的字符串不属于基本数据类型,而是作为String类的[匿名对象](https://www.cnblogs.com/myworld7/p/9977000.html)而存在。
验证"Sakura"字符串是否为匿名对象:

"Sakura"可以调用String类的方法,由此可见"Sakura"是一个对象。
创建String对象的直接赋值方式相当于将一个匿名对象设置了一个名字。在前篇文章里我们说直接使用"new 类名称();"的方法创建的是一个匿名对象,String类的匿名对象却不是这样的,String类的匿名对象是由系统自动生成不是由用户直接创建。
下面会讲JVM中关于字符串的内存分配管理。
| Notice |
图中的代码实际隐含了一个避免出现NullPointerException的小技巧。
若是我们像下面这样写字符串比较代码:

未知name1是否指向了一个对象,所以会存在抛出空指针异常的情况。为了避免空指针异常我们就可以将字符串常量写在前面。

两种实例化方式的区别
直接赋值方式
前面讲过直接赋值方式就是将一个字符串的匿名对象设置了一个名字。

"=="比较的是两个对象内存地址是否一致,由输出结果可以看出name1和name2指向了同一块堆空间。

为什么不是在堆空间中开辟两个"Sakura"对象而是让name1和name2指向同一个对象呢?
这个需要谈到JVM的共享设计模式。
JVM的底层实现实际上在堆中存在一个对象池(常量池,不一定只保存String对象),当我们使用直接赋值方式定义String类对象,那么JVM会将此字符串对象使用的匿名对象就是如"Sakura"字符串入池保存。如果后面还有其他String对象采用同样方式且设置同样内容时,将不会开辟新的堆空间,而是继续使用相同的空间。

采用构造方法实例化
String name = new String("Sakura");
分析以上语句开辟空间情况:
开辟了一块栈内存存储了对象引用; 开辟了两块堆空间,一块在常量池中存储"Sakura"字符串常量另一块在堆中存储这个对象。

当堆中的对象若是没有引用指向就是垃圾对象会被GC清理掉。所以,这种构造方式会造成一块堆空间的浪费。
若是希望,此方式的对象也可以入池保存也是有方法的,就是利用String类的intern方法。
public class Test {
public static void main(String[] args) {
String name1 = new String("Sakura").intern(); //返回一个匿名对象 name1就指向的是常量池中的"Sakura"
String name2 = "Sakura";
System.out.println(name1==name2);
}
}
/*
output:
true
*/
但是方法真的显得很麻烦!
总结一下两种实例化方法的区别:
- 直接赋值方式:只会开辟一块堆内存空间,并且自动保存在常量池中,以供我们下次重复使用。
- 构造方法:会开辟两块堆内存空间,其中在常量池的会成为垃圾空间。
字符串一旦定义便不可变
String name = "Amy";
String name = "Smith";
String name = "Amy" + "Smith";
Java定义了String内容不能被改变。分析堆内存,是可以知道字符串对象内容的改变是利用了引用关系的变化而实现的。每一次的改变都会产生垃圾空间,所以不要频繁更改定义好的字符串。

字节与字符串
查看API可以看见有许多关于字节的方法。字节使用byte描述,是Java中的基本数据类型之一,使用字节一般主要用于数据的传输或者进行编码转换的时候使用。在String中有许多将字符串转换为字节数组的操作,目的就是为了传输转换。
字符串中的方法分类
在程序开发中对字符串的操作是常事的,那么在Java中对字符串操作方法也是有很多的。主要分为下面几类,关于每种方法的使用可以查看API,但是最好还是几乎要掌握完。
- 比较方法
- 查找方法
- 替换方法
- 截取方法
- 拆分方法
重载"+"与StringBuilder
Java中不允许程序员重载任何操作符,但是Java内部重载了两个用于String类的操作符"+"和"+="。操作符"+"可以用于连接字符串,操作符"+="用于将连接后的字符串再次赋给原字符串引用。
由前面所知,不断使用"+="连接,会产生很多的中间垃圾对象,而且连接的越多也就越浪费空间和时间。垃圾对象占用空间,Java垃圾回收器清理越耗时。
虽然使用这种方式连接字符串,从分析堆栈图来看很费空间和耗时。但是,JVM在运行程序会不会给优化呢。我们反编译下面程序来观察一下:
javap -c StringContact
public class StringContact {
public static void main(String[] args) {
String str1="hello";
String str2="Sakura"+str1+"!";
System.out.println(str2);
}
}
/*
output:
Sakurahello!
*/

可以看出:编译器自动引入了java.lang.StringBuilder类。编译器先自动创建了一个StringBuilder对象,每次字符串连接时调用StringBuilder的append()方法,调用了两次。最后,调用toString()生成最终的字符串,存在str2中使用命令astroe_2。
编译器自主使用StringBuilder类,因为它更高效。StringBuilder对象含有一个缓冲区来处理字符串,所以可以修改删除字符串。在上面代码中创建了一个StringBuilder对象,连接字符串时只是不断调用其append方法,没有创建反复创建对象。
使用下面的例子深入看看编译器的优化程度:
public class CompareStringBuilder {
public String implicit(String[] fields) { //使用String隐式的字符串连接
String result="";
for(int i=0; i<fields.length; i++)
result += fields[i];
return result;
}
public String explicit(String[] fields) { //使用StringBuilder的append方法连接字符串
StringBuilder result=new StringBuilder();
for(int i=0; i<fields.length; i++)
result.append(fields[i]);
return result.toString();
}
}
反编译上述程序:

若是不满足Code 8的大于等于循环次数的话,那么Code 5到Code 35就是一个循环,并且在每一次循环中StringBuilder对象都会被创建。

可以看出这个代码只是在最初创建了一次StringBuilder对象,并且在循环中是一直使用append()方法修改字符串。
以上两段代码可以看出编译器对我们代码的优化程度,字符串较简单时可以直接使用String,若是需要大量连接则需要可考虑StringBuilder类。
StringBuilder与StringBuffer
与String对象比较StringBuffer是一个可变的对象,可以通过其自带的某些方法改变其值的长度和内容。如使用append()方法追加字符串。
同StringBuilder一样,StringBuffer对字符串的修改效率要高于String。
查看JDK文档可知,StringBuilder是在JAVA 5中提出,与StringBuffer拥有的方法几乎相似,可以看成StringBuffer一种“替换”形式。二者的主要区别是,StringBuffer是线程安全的,而StringBuilder是线程不安全的。
查看JDK源码,可以发现StringBuffer在其每个方法前都加了synchronized关键字(用于多线程线程同步)
如append方法
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
因为StringBuilder是线程不安全的,所以一般用在单线程,因为其不需要管理线程同步这些问题所以速度会比StringBuffer快。
小结
本文主要介绍了:
String对象使用equals的比较对象是否相等以及使用"=="判断对象是否同一对象;String对象的两种实例化方式,直接赋值不会产生垃圾空间,并且会存入常量池中,构造方法会产生中间垃圾对象且不会入池;String对象是一个不可变对象,String类对象内容改变是依靠引用关系变化,实际对象并没有发生任何变化;若是经常改变字符串值需要使用StringBuilder或者StringBuffer。
部分内容参考自《Java编程思想》(第四版)
Java——String对象的更多相关文章
- JAVA String对象和字符串常量的关系解析
JAVA String对象和字符串常量的关系解析 1 字符串内部列表 JAVA中所有的对象都存放在堆里面,包括String对象.字符串常量保存在JAVA的.class文件的常量池中,在编译期就确定好了 ...
- 深入理解java String 对象的不可变性
下面我们通过一组图表来解释Java字符串的不可变性 1.声明一个String对象 String s = "abcd"; 2.将一个String变量赋值给另一个String变量 St ...
- 关于Java String对象创建的几点疑问
我们通过JDK源码会知道String实质是字符数组,而且是不可被继承(final)和具有不可变性(immutable).可以如果想要了解String的创建我们需要先了解下JVM的内存结构. 1.JVM ...
- Java String对象的问题 String s="a"+"b"+"c"+"d"
1, String s="a"+"b"+"c"+"d"创建了几个对象(假设之前串池是空的) 2,StringBuilde ...
- 我的Java开发学习之旅------>Java String对象作为参数传递的问题解惑
又是一道面试题,来测试你的Java基础是否牢固. 题目:以下代码的运行结果是? public class TestValue { public static void test(String str) ...
- Java String对象的经典问题
先来看一个样例,代码例如以下: public class Test { public static void main(String[] args) { Strin ...
- Java String对象的经典问题(转)
public class StringTest { public static void main(String[] args) { String strA = "abc"; St ...
- Java String 对象,你真的了解了吗?
String 对象的实现 String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能,看下面这张图,一起了解一 ...
- java String对象的创建(jvm).
本人目前也开始学习虚拟机,在java中,有很多种类型的虚拟机,其中就以sum公司(当然现在已经是oracle了)的虚拟机为例,介绍可能在面试的时候用到的,同时对自己了解String有很大帮助,这里仅仅 ...
随机推荐
- python set所用后列表不改变里面内容排序
my_list = [1,2,1,54,5,64,4681,4,676] my_list_two = list(set(my_list)) my_list_two.sort(key = my_list ...
- HTML标签的绝对路径和相对路径
我在javaweb中写json的Demo的时候遇到了这个问题,图片一一直取不出来,查了好久终于解决了,所以现在记录一下. 绝对路径: 其实很容易理解,如果你是一个普通的项目,那就是它在你电脑里真实存在 ...
- python之常用模块4
pyinotify模块 pip3 install pyinotify pyinotify提供的事件: 事件标志 事件含义 IN_ACCESS 被监控项目或者被监控目录中的文件被访问,比如一个文件被读取 ...
- day_9内存管理
复习 '''文件处理1.操作文件的三步骤 -- 打开文件:硬盘的空间被操作系统持有 | 文件对象被应用程序持续 -- 操作文件:读写操作 -- 释放文件:释放操作系统对硬盘空间的持有 2.基础的读写 ...
- Icehouse 创建Instance代码分析
1. nova-api接收到request 在/etc/nova/api-paste.ini中,是这样配置nova v2的 [app:osapi_compute_app_v2] paste.app_f ...
- 升讯威微信营销系统开发实践:所见即所得的微官网( 完整开源于 Github)
GitHub:https://github.com/iccb1013/Sheng.WeixinConstruction因为个人精力时间有限,不会再对现有代码进行更新维护,不过微信接口比较稳定,经测试至 ...
- Java 线程池(ThreadPoolExecutor)原理解析
在我们的开发中“池”的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 有关java线程技术文章还可以推荐阅读:<关于java多线程w ...
- Javascript高级编程学习笔记(54)—— DOM2和DOM3(6)范围选择
范围 为了让开发人员更加方便地控制页面“DOM2级遍历和范围”模块定义了“范围”接口 通过该接口开发人员可以选择文档中的一个区域,而不必考虑元素的界限 在常规操作不能有效地修改文档时,使用范围往往可以 ...
- Node.js(day2)
一.使用Node实现基本Apache的功能 在上一篇笔记中,我们提到如果打开一个文件需要进行一次url判断是繁琐的,我们希望我们的Node具有类似Apache这种web服务器的一个功能:将文件放到ww ...
- new 操作符 做了什么
new 操作符 做了什么 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例. 假设Test是一个构造函数,通常在创建对象的实例时,要使用new,eg:test = new ...