String详解

注意区分对象和对象的引用

首先来看一下我在jdk中找到的String源代码,这里只截取开头的小小一部分

public final class String

implements java.io.Serializable, Comparable, CharSequence {

/** The value is used for character storage. */

private final char value[];

从这里可以看出,String类是被final所修饰的,因此String类对象不可变,也不可继承。这里要注意一个误区,字符串对象不可变,但字符串变量所指的值是可变的,即引用地址可变。String变量存储的是对String对象的引用,String对象里存储的才是字符串的值【注意区分对象和对象的引用】。看下面的例子

String str = "abc"; //str是个对象引用

System.out.println(str); //输出abc

str = "abcde"; //不会出错

System.out.println(str); //输出abcde

当给str第二次赋值的时候,对象"abc"并没有被销毁,仍存放在常量池中(String自带),只是让str指向了"abcde"的内存地址,而字符串对象"abcde"是新生成的,即str第二次赋值时并不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。记住,对String对象的任何改变都不影响到原对象,相关的任何改变的操作都会生成新的对象。

String对象是否真的不可变?!

答案并不是的,那么该如何做呢?那就是使用反射(反射不太懂的可以点击链接学习——

从上面的源码可知String的成员变量是private final的,也就是初始化之后不可改变。那么在这几个成员中,value比较特殊,因为它是一个引用变量,而不是真正的对象。value是final修饰的,即final不能再指向其他数组对象,那么我想改变value指向的数组时,比如将数组中的某个位置上的字符变为下划线"_"。因为不能直接通过这个引用去修改数组,那么为了访问到至value这个引用,我们可以使用反射来访问私有成员,反射出String对象中的value属性,进而改变value数组的内容。

public static void testReflection() throws Exception {

// 创建字符串"Hello World" 并赋给引用s

String s = "Hello World";

System.out.println("s = " + s); // 打印出Hello World

// 获取String类中的value字段————Field类获取成员变量

Field valueFieldOfString = String.class.getDeclaredField("value");

valueFieldOfString.setAccessible(true); // 改变value属性的访问权限

value[5] = '_'; // 获取s对象上的value属性的值 改变value所引用的数组中的第5个字符

System.out.println("s = " + s); // 打印出Hello_World

}

下面这个例子将引出两个问题

String str="hello world" 和 String str=new String("hello world") 有什么不同

为什么使用 "==" 和 "equals" 会有不同的结果public static void main(String[] args) {

String str1 = "hello world";

String str2 = new String("hello world");

String str3 = "hello world";

String str4 = new String("hello world");

String str5 = "Hello" + " World";

String str6 = "Hello" + new String(" World");

String str7 = str2.intern();

String ex1 = "Hello";

String ex2 = " World";

String str8 = ex1 + ex2;

System.out.println(str1 == str3); //true

System.out.println(str1 == str2); //false

System.out.println(str2 == str4); //false

System.out.println(str1.equals(str2)); //true

System.out.println(str2.equals(str4)); //true

System.out.println(str1 == str5); //true

System.out.println(str1 == str6); //false

System.out.println(str1 == str7); //true

System.out.println(str1 == str8); //false

}

【运行结果】是 true、false、false、true、true、true、false、true、false

先把下面四大点看懂了,我会在最后写出【解析】

String的两种赋值方式

※ 区分【String str="HW"】和【String str=new String("HW")】

(1)字面量赋值方式     eg:String str = "Hello";

该种直接赋值的方法,JVM会去字符串常量池(String对象不可变)中寻找是否有equals("Hello")的String对象,如果有,就把该对象在字符串常量池中"Hello"的引用复制给字符串变量str,如若没有,就在堆中新建一个对象,同时把引用驻留在字符串常量池中,再把引用赋给字符串变量str。

用该方法创建字符串时,无论创建多少次,只要字符串的值(内容)相同,那么它们所指向的都是堆中的同一个对象。

该方法直接赋值给变量的字符串存放在常量池里

(2)new关键字创建新对象     eg:String str = new String("Hello");

利用new来创建字符串时,无论字符串常量池中是否有与当前值相同的对象引用,都会在堆中新开辟一块内存,创建一个新的对象。

注意:对字符串进行拼接操作,即做"+"运算的时候,分2种情况:

表达式右边是纯字符串常量,那么存放在常量池里面。eg:String str = "Hello" + "World";

表达式右边如果存在字符串引用,也就是字符串对象的句柄,那么就存放在堆里面。eg:String str = str1 + str2;

总结:常量池是方法区的一部分,而方法区是线程共享的,所以常量池也是线程共享的,且它是线程安全的,它让有相同值的引用指向同一个位置,如果引用值变化了,但是常量池中没有新的值,那么就会新建一个常量结果来交给新的引用,对于同一个对象,new出来的字符串存放在堆中,而直接赋值给变量的字符串存放在常量池里。

​​​​​​

字符串比较——区分"=="和"equals"

"==":比较引用变量的地址,即两个对象是否引用同一地址的内容,用"=="时会检测是否指向同一个对象

"equals":比较对象的内容,即两个对象内容上是否相同

字符串用这两种比较方式都是可行的,具体看想要比较什么,总体来看,"=="稍微强大些,因为它既要求内容相同,也要求引用对象相同

intern() 方法

当使用 intern()方法时,会先查询字符串常量池是否存在当前字符串,如果存在,则返回常量池中的引用,若不存在,则将字符串添加到字符串常量池中,并返回字符串常量池中的引用。

【代码解析】

str1 == str3——str1与str3指向常量池中同一个对象,引用对象相同,因此用"=="比较时结果为true

str1 == str2——new会创建一个新的对象放在堆中,str1所指对象在常量池,即使str1与str2内容相同,但并不是同一个对象

str2 == str4——new会创建一个新的对象放在堆中,str2与str4指向不同的对象,即使内容相同

str1.equals(str2)——str1于str2各自引用对象不同,但内容相同,因此用"equals"比较时结果为true

str2.equals(str4)——str2于str4各自引用对象不同,但内容相同,因此用"equals"比较时结果为true

str1 == str5——对字符串进行拼接操作("+"运算)时,表达式右边是纯字符串常量,那么存放在常量池里面,若常量池中有该字符串,则返回该引用

str1 == str6——对字符串进行拼接操作("+"运算)时,表达式右边如果存在字符串引用,也就是字符串对象的句柄,那么就存放在堆里面

str1 == str7——intern()方法会去常量池中找是否存在当前字符,存在则返回引用,该对象引用刚好是str1所指向的

str1 == str8——str8由两个变量拼接,编译期不知道它们的具体位置,所以不会做出优化,必须要等到运行时才能确定,因此新对象的地址和前面的不同。

StringBuilder  & StringBuffer

StringBuilder和StringBuffer类类似于String类,但区别在于String创建的对象是不可改变的,而StringBuilder和StringBuffer这两个类创建对象后都是可以对对象进行修改的。即String为字符串常量,而StringBuilder和StringBuffer均为字符串变量

运行速度(快到慢):StringBuilder > StringBuffer > String

StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行像String对象那样子进行创建和回收的操作,所以速度要比String快很多。

在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的

如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

总结

String:适用于少量的字符串操作的情况

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

字符串拼接五种方法

使用 +

使用 concat

使用 StringBuilder

使用 StringBuffer

使用 StringUtils.join

注:先看完上面对String、StringBuilder和StringBuffer的详解后再看下面的文章会好理解很多的

由于String是Java中一个不可变的类,所以他一旦被实例化就无法被修改,因此所有的所谓字符串拼接,都是重新生成了一个新的字符串。

效率(用时短到长):StringBuilder

(1)"+"是Java提供的一个语法糖,而使用+拼接的字符串,它将String转成了StringBuilder后,再使用StringBuilder.append进行处理。如果不是在循环体中进行字符串拼接的话,直接使用+就好了。

(2)concat方法,其实是new了一个新的String

(3)StringUtils.join也是通过StringBuilder来实现的

(4)StringBuffer在StringBuilder的基础上,做了同步处理,所以在耗时上会相对多一些。

(5)如果在并发场景中进行字符串拼接的话,要使用StringBuffer来替代StringBuilder。因为StringBuilder是线程不安全的,而StringBuffer是线程安全的

其他参考文章:

java字符串拼接例子_Java详解【String】+【StringBuilder vs StringBuffer】+【字符串拼接】...相关推荐

  1. angular字符串转成html,详解angular如何调用HTML字符串的方法

    详解angular如何调用HTML字符串的方法 前面的文章我们介绍过angular6.0的数据绑定,也就是前面页面如何调用后台的数据,我们接触到了调用普通数据--如:调用产品详情{{post.cont ...

  2. char数组反转java_java 字符串反转的实例详解

    java 字符串反转的实例详解 1.new StringBuffer("abcde").reverse().toString(); 2.通过char数组进行转换, 代码如下 pac ...

  3. 【Java 8 新特性】Java 8 Util API: StringJoiner 详解 | 拼接字符串添加分隔符、前缀和后缀

    Java 8 Util API: StringJoiner 详解 StringJoiner(CharSequence d) StringJoiner.add(CharSequence element) ...

  4. java生成字符串数组_Java 生成随机字符串数组的实例详解

    Java 生成随机字符串数组的实例详解 利用Collections.sort()方法对泛型为String的List 进行排序.具体要求: 1.创建完List之后,往其中添加十条随机字符串 2.每条字符 ...

  5. java中的进制输出转换_Java I/O : Java中的进制详解

    作者:李强强 上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算.这一讲,泥瓦匠带你走进Java中的进制详解. 一.引子 在Java世界里,99%的工作都是处理这高层. ...

  6. java源码详解——String类

    java源码详解--String类目录: Java String 类 下面开始介绍主要方法: Java charAt() 方法 Java compareTo() 方法 int compareTo(St ...

  7. 面试-Java【之】(revers)递归实现字符串倒序排列(详解)

    面试-Java[之](revers)递归实现字符串倒序排列(详解) 实现源码详解 <目录:Java-JDBC学习> <幕> 实现源码详解 public class Test { ...

  8. Java之格式化字符串及格式化输出详解

    Java之格式化字符串及格式化输出详解: 格式化输出: Java 5引入了与C语言的printf函数风格类似的format方法和printf方法,这两个方法可用于java.io.PrintStream ...

  9. c语言标准库详解(七):字符串函数string.h

    c语言标准库详解(七):字符串函数<string.h> 头文件<string.h>中定义了两组字符串函数.第一组函数的名字以 str 开头:第二组函数的名字以 mem 开头.除 ...

最新文章

  1. php中OR与|| AND与的区别
  2. DeepMind难以盈利,人工智能该走向何处去?
  3. 黄聪:Dsicuz x2.5、X3、X3.2如何去掉域名后面的/forum.php
  4. 【疼逊】致广大QQ用户的一封信
  5. 【NLP】3篇论文,看斯坦福团队如何构建更好用的聊天AI
  6. 【MYSQL命令】查看一个表的建表语句
  7. html怎么查看两个块的距离,两个东西之间的距离怎么控制
  8. Linux最终将会领先于Windows、Mac OS!
  9. Python学习-文件的调用-读取
  10. oracle10g 克隆安装,克隆Oracle Home(10g2)
  11. python语言用什么关键字来声明一个类_Python语言和标准库(第三章:类和对象)...
  12. 《Java8实战》笔记(16):结论以及Java的未来
  13. Qt|C++工作笔记-QVector与Vector去重复的值
  14. datetime-local 传时间戳_传世经典,此生必读——《呼兰河传》
  15. 福建水院计算机一级清考,取消毕业“清考”大学生还敢混日子吗
  16. Android重力感应实现方式简介
  17. android mds文件,安卓手机如何打开.mdf文件
  18. 嵩天python笔记_嵩天Python学习笔记
  19. fgo服务器维护补偿什么时候才有,FGO11月02日临时维护公告 补偿奖励一览
  20. Tarjan算法附图详解(SCC)

热门文章

  1. ASP.NET WebAPI 中的参数绑定
  2. 以深圳.NET俱乐部名义 的技术交流会圆满成功
  3. powershell XML数据保存为HTML
  4. 释放mysql ibdata1文件_释放MySQL ibdata1文件的空间
  5. 【CC精品教程】ContextCapture 4.4.12(CC,Smart 3D)简体中文版安装教程(附安装包下载)
  6. 【计算机图形学】实验:C#语言采用GDI+定义笔刷并填充图形完整实验操作流程
  7. CAD格式数据转ArcGIS数据方法总结
  8. open ssl里面的自定义get***函数失效
  9. Android开发之运行客户的Demo拿不到数据
  10. Android之PowerManager简介