前言


实际上任何语言都没有提供字符串这个概念,而是使用字符数组来描述字符串。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. Buffer.h

    #ifndef __NOXIMBUFFER_H__ #define __NOXIMBUFFER_H__ #include <cassert> #include <queue> ...

  2. intent和手势探测

    一.三种启动方法 setComponent ComponentName comp = new ComponentName( this, SecondActivity.class); Intent in ...

  3. Node.js创建本地简易服务器

    创建简易的本地服务器 安装node.js 在项目下,通过npm init -y创建package.json文件 通过npm install mime --save加载mime插件 创建server.j ...

  4. noip第28课资料

  5. Android MonkeyRunner

    MonkeyRunner 也是一款安卓sdk自有的测试工具 可以用来做功能测试 阅读目录 MonkeyRunner环境搭建 1. 安装JDK的安装与配置 2. 安装Python编译器     http ...

  6. ASP.NET MVC项目中App_Code目录在程序应用

    学习ASP.NET MVC,如果你是开发ASP.NET MVC项目的,也许你去为项目添加前ASP.NET项目的APP_Code目录,在这里创建与添加的Class类,也许你无法在MVC项目所引用. 那这 ...

  7. 【Vue】谈Vue的依赖追踪系统 ——搞懂methods watch和compute的区别和联系

    从作用机制和性质上看待methods,watch和computed的关系 图片标题[原创]:<他三个是啥子关系呢?> 首先要说,methods,watch和computed都是以函数为基础 ...

  8. 【百度杯】ctf夺旗大战,16万元奖励池等你拿

    寻找安全圈内最会夺flag的CTF职业玩家,将以个人方式参与夺旗,完全凭借个人在CTF中的技艺及造诣获得奖金回报. 周末少打一局LOL,玩一玩CTF也能挣个万元零花钱! **比赛时间: 9月至17年3 ...

  9. IDEA环境下GIT操作浅析之二-idea下分支操作相关命令

    上次写到<idea下仓库初始化与文件提交涉及到的基本命令>,今天我们继续写IDEA环境下GIT操作之二--idea下分支操作相关命令以及分支创建与合并. 1.idea 下分支操作相关命令 ...

  10. 转:Python: 什么是*args和**kwargs

    今天看源码的时候发现一个是*args和**kwargs,一看就能知道args是神马,就是所有参数的数组,kwargs就不知道了,google一下,一个人的blog写的比较简单明了,秒懂了~~kwarg ...