前言


实际上任何语言都没有提供字符串这个概念,而是使用字符数组来描述字符串。Java里面严格来说也是没有字符串的,在所有的开发里面字符串的应用有很多,于是Java为了应对便创建了String类这个字符串类。使用""定义的内容都是字符串,理解Java的String类需要从类的角度和内存关系上分析这个类。

下面将介绍:

  • String类对象的两种实例化方式
  • 使用"=="和equals比较字符串是否相等
  • String常量为匿名对象
  • String两种实例化方式的区别
  • 字符串一旦定义则不可变
  • 字节与字符串
  • 字符串中的方法分类
  • 重载"+"与StringBuilder
  • StringBuffer与StringBuilder

String类对象的两种实例化方式


String name1 = "Sakura";    //直接赋值方式
String name2 = new String("Sakura"); //利用构造方法实例化

使用"=="和equals比较字符串是否相等


![](https://img2018.cnblogs.com/blog/1099419/201811/1099419-20181118181605694-457490419.png)

使用"=="比较的是两个对象在内存中的地址是否一致,也就是比较两个对象是否为同一个对象。

使用equals()方法比较的是对象的值是否相等,name1和name2所指对象的值都是"Sakura"所以输出为true。

String常量为匿名对象


像"Sakura"这样的字符串不属于基本数据类型,而是作为String类的[匿名对象](https://www.cnblogs.com/myworld7/p/9977000.html)而存在。

验证"Sakura"字符串是否为匿名对象:

![](https://img2018.cnblogs.com/blog/1099419/201811/1099419-20181118181653010-1754116119.png)

"Sakura"可以调用String类的方法,由此可见"Sakura"是一个对象。

创建String对象的直接赋值方式相当于将一个匿名对象设置了一个名字。在前篇文章里我们说直接使用"new 类名称();"的方法创建的是一个匿名对象,String类的匿名对象却不是这样的,String类的匿名对象是由系统自动生成不是由用户直接创建。

下面会讲JVM中关于字符串的内存分配管理。

Notice

图中的代码实际隐含了一个避免出现NullPointerException的小技巧。

若是我们像下面这样写字符串比较代码:

![](https://img2018.cnblogs.com/blog/1099419/201811/1099419-20181118181813068-27980081.png)

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

两种实例化方式的区别


直接赋值方式

前面讲过直接赋值方式就是将一个字符串的匿名对象设置了一个名字。

![](https://img2018.cnblogs.com/blog/1099419/201811/1099419-20181118181846255-806130668.png)

"=="比较的是两个对象内存地址是否一致,由输出结果可以看出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对象的更多相关文章

  1. JAVA String对象和字符串常量的关系解析

    JAVA String对象和字符串常量的关系解析 1 字符串内部列表 JAVA中所有的对象都存放在堆里面,包括String对象.字符串常量保存在JAVA的.class文件的常量池中,在编译期就确定好了 ...

  2. 深入理解java String 对象的不可变性

    下面我们通过一组图表来解释Java字符串的不可变性 1.声明一个String对象 String s = "abcd"; 2.将一个String变量赋值给另一个String变量 St ...

  3. 关于Java String对象创建的几点疑问

    我们通过JDK源码会知道String实质是字符数组,而且是不可被继承(final)和具有不可变性(immutable).可以如果想要了解String的创建我们需要先了解下JVM的内存结构. 1.JVM ...

  4. Java String对象的问题 String s="a"+"b"+"c"+"d"

    1, String s="a"+"b"+"c"+"d"创建了几个对象(假设之前串池是空的) 2,StringBuilde ...

  5. 我的Java开发学习之旅------>Java String对象作为参数传递的问题解惑

    又是一道面试题,来测试你的Java基础是否牢固. 题目:以下代码的运行结果是? public class TestValue { public static void test(String str) ...

  6. Java String对象的经典问题

     先来看一个样例,代码例如以下:  public class Test {       public static void main(String[] args) {           Strin ...

  7. Java String对象的经典问题(转)

    public class StringTest { public static void main(String[] args) { String strA = "abc"; St ...

  8. Java String 对象,你真的了解了吗?

    String 对象的实现 String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能,看下面这张图,一起了解一 ...

  9. java String对象的创建(jvm).

    本人目前也开始学习虚拟机,在java中,有很多种类型的虚拟机,其中就以sum公司(当然现在已经是oracle了)的虚拟机为例,介绍可能在面试的时候用到的,同时对自己了解String有很大帮助,这里仅仅 ...

随机推荐

  1. es6面向对象

    <script> class user{ constructor(name,age){ this.name=name; this.age=age; } showName(){ alert( ...

  2. C++: cin

    cin字符的时候, 会忽略掉'\n', ' '等空白符

  3. Multi-pattern string match using Aho-Corasick

    我是擅(倾)长(向)把一篇文章写成杂文的.毕竟,写博客记录生活点滴,比不得发 paper,要求字斟句酌八股结构到位:风格偏杂文一点,也是没人拒稿的.这么说来,arxiv 就好比是 paper 世界的博 ...

  4. python insert所用 插入到自定的位置

    a = list(range(50)) b = list(range(50)) c = [] for x in a: c.insert(x, [a[x], b[x]]) print(c)

  5. 关于http与https的注意点

    背景:在一次项目生产上线中遇到地址在IOS版本的app中打不开或者接口请求不返回的情况,在安卓机和PC上表现正常,经排查,问题出在http请求上,原因详解 在早期PC上和安卓手机上比较不严格,在htt ...

  6. 安装kylin的艰难历程

    前言:暑假里老师布置的任务没有完成,来到学校后马不停蹄的安装kylin,结果一路艰难险阻,搞了快两个星期都没有弄好....现在止步于hive阶段卡死...仅将之前的步骤记录下来以便重新安装时更加顺利. ...

  7. 2017-12-04 编写Visual Studio Code插件初尝试

    参考官方入门: Your First Visual Studio Code Extension - Hello World 源码在: program-in-chinese/vscode_helloWo ...

  8. 背水一战 Windows 10 (119) - 后台任务: 后台下载任务(任务分组,组完成后触发后台任务)

    [源码下载] 背水一战 Windows 10 (119) - 后台任务: 后台下载任务(任务分组,组完成后触发后台任务) 作者:webabcd 介绍背水一战 Windows 10 之 后台任务 后台下 ...

  9. Java 线程池(ThreadPoolExecutor)原理解析

    在我们的开发中“池”的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 有关java线程技术文章还可以推荐阅读:<关于java多线程w ...

  10. 纯小白创建第一个Node程序失败-容易忽略的一个细节

    一直觉得自己基础还很差,所以自觉不敢去碰node.js,但又对其心怀好奇.恰巧最近有一点空闲时间,忍不住去试了一下水   这不,在创建第一个node程序时就吃了闭门羹,总是提示我没有定义,如下图, 这 ...