转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10840495.html

一:字符串的不可变性

1、可变 与 不可变 辨析

Java中的对象按照创建后,对象的内容是否可以被修改,分为 mutable object 和 immutable object。【注意:是对象的内容不可变,而不是指向该对象的引用变量内容不可变。】

我们常见的不可变对象是几个基本数据类型的包装类——Integer、Double、String等。【想想为什么?——Tips:出于节省内存开销,避免重复创建。】

不可变类有5大基本原则:

1)类定义时,添加final修饰符,保证类不被继承【即:不允许在子类中被修改】

    2)类定义时,其成员变量一概使用 final private 修饰,保证变量私有的同时不允许修改

3)不提供可以修改成员变量的方法,包括setter

4)在构造函数中采用deep copy的形式将参数值拷贝给成员变量,而不是直接将参数值赋给成员变量【因为引用类型的参数只是传了一个地址,这样在外部改变该地址的内容会导致不可变对象的成员变量改变】

5)在成员变量的getter方法中,不能直接返回成员变量本身,而是返回成员变量的copy对象【这也是为了防止引用类型的成员变量被外部获取后,改变引用指向的对象值引起不可变对象的内容变化】

2、不可变对象的优缺点

1)字符串常量池的需要——避免每次使用相同的字符串常量都重新创建相同的对象、节省存储空间

    2)线程安全:当一个String对象被多个线程共享时,无需担心线程安全问题

3)支持hash和缓存:由于字符串对象是不可变的并且hashcode就缓存在对象中【在下文讲解】了,不需要重新计算,因此很适合作为Map中的键,因为字符串键的哈希处理速度要快过其它的键对象【这就是HashMap中的键往往都使用字符串的原因】

4)使用类加载器要用字符串来传递加载的类名,而字符串的不可变性提供了安全性,以保证正确的类被加载。

5)  缺点:当对String变量有重新赋值、修改等操作时,会不断创建大量的String对象。【当修改后的值未出现在字符串常量池的前提下】————【延伸:因此,在代码中涉及大量字符串操作时,使用StringBuilder或StringBuffer来进行

3、“不可变对象”的非常规手段修改

对于不可变对象,可以通过反射机制的手段改变其值——获取类的字段定义->改变该字段的可见性和可修改性->修改对象的变量值

例如:

  //创建字符串"Hello World", 并赋给引用s
String s = "Hello World";
System.out.println("s = " + s); //Hello World //获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true); //获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World 结果为:
s = Hello World
s = Hello_World

二:replaceFirst、replaceAll、replace 区别

我们先来看一下程序:

String s = "my.test.txt";
System.out.println(s.replace(".", "#"));
System.out.println(s.replaceAll(".", "#"));
System.out.println(s.replaceFirst(".", "#"));

它们的结果会一样吗?——No。

my#test#txt
###########
#y.test.txt

这三个函数中,replaceFirst、replaceAll在替换时使用了正则表达式,因此上面三个函数的参数含义是不同的

replace(src,des):将字符串中的src子串[也是一段字符串]替换成des。

replaceAll(reg,des):将字符串中符合reg模式的内容替换成des。

replaceFirst(reg,des):将字符串中,第一个匹配reg模式的内容替换成des。

所以上面示例代码中,replace函数是将字符串中的“.”字符换成“#”,而replaceAll则是将所有字符[“.”是正则表达式的通配符]替换成“#”,replaceFirst则是将第一个字符替换成“#”。

三:String对“+”运算符的重载

“在Java中是不支持重载运算符的!”

java不支持运算符重载,因为java的语法比较繁杂,会导致使用类对象 像基本数据类型那样 用运算符进行操作时,无法做到像c++一样流畅。因此,Java中针对类对象的运算操作一般都是通过方法来定义,而不是运算符重载。

唯一例外的是String类,它的拼接运算(+) 经过了重载,这个重载是通过jvm编译实现的,具体原理可以 手写一个字符串相+的java类文件并编译,然后通过 javap -c 文件.class 查看具体的过程。

其原理是:String的+会被转化为StringBuilder的append方法,并生成一个新的String对象返回。

四:字符串拼接的5种方式比较

Java中有以下五种方法处理字符串拼接:

1. 加号 “+”:适用于小数据量的操作,代码简洁方便。

2. String contact() 方法:适用于小数据量的操作,代码简洁方便。

3. StringUtils.join() 方法:适用于将ArrayList转换成字符串的情景,可以省掉用for循环读取ArrayList手动拼接的过程。

4. StringBuffer append() 方法:继承自AbstractStringBuilder,效率高,大批量的数据处理的好选择,该方法线程安全,由于加了线程锁,速度会比下面第5中慢一点。

5. StringBuilder append() 方法:继承自AbstractStringBuilder,效率最高,大批量的数据处理的好选择,该方法线程不安全,因此速度最快,如果不涉及多线程操作,优先使用此方法。

五:String.valueOf(val) 和 obj.toString 的异同

同:都返回参数的字符串表示形式。

异: 对空值的调用结果不同:

java.lang.Object类里已有public方法.toString(),所以对任何严格意义上的java对象都可以调用此方法。但在使用时要注意,必须保证object不是null值,否则将抛出NullPointerException异常。

而valueOf(Object obj)对null值进行了处理,不会报任何异常。但当object为null 时,String.valueOf(object)的值是字符串”null”,而不是null。

六:switch 对 String 的支持

Java1.7之前,switch只能局限于int 、short 、byte 、char四类做判断条件,因为在JVM内部实际大部分字节码指令只有int类型的值。

在使用switch的时候,如果是非int型,会先转为int型,再进行条件判断

但是在Java1.7中,switch增加了对String作为判断条件的支持,可String并不能直接转为int型,这是怎么做到的呢?

原理:switch比较的是字符串常量的哈希值(缓存的int类型值,前文提到过),但是hash值可能会有冲突,所以还需要再调用equals方法将 switch(param)的param 与 case str的str 进行二次比较,二者综合之下达到唯一匹配的目的。

七:字符串池、常量池(运行时常量池、Class 常量池)、intern

全局字符串常量池(string pool):全局常量池在每个JVM中只有一份,存放的是字符串常量值。

string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的),
                                                字符串在第一次出现时被创建并且把引用放到stringtable中,在后面再出现时就不会重复创建而是直接从stringtable中找到字符串字面值的地址,返回给字符串引用变量。

Class常量池:class常量池是在编译的时候每个class都有的,用于编译阶段,存放的是class文件中的字面量(常量)和符号引用

字面量就是我们所说的常量,如文本字符串、八种基本类型的值、被声明为final的常量值等。

符号引用是一组符号,用来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
                       直接引用是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄。

运行时常量池:运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,将符号引用替换成直接引用,与全局常量池中的引用值保持一致

运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是String类的intern()方法

str.intern()函数:把字符串对象str加入常量池中,如果常量池中已有该字符串字面值,则返回stringtable中的引用值。(这样做主要是为了避免在堆中不断地创建新的字符串对象)

常量池我们都知道他是存在于方法区的,他是方法区的一部分,而方法区是线程共享的,所以常量池也就是线程共享的,但是他并不是线程不安全的,他其实是线程安全的,因为它让有相同值的引用指向同一个位置。如果引用值变化了,但是常量池中没有新的值,那么就会新开辟一个常量结果来交给新的引用。

详细原理请看:https://www.cnblogs.com/ygj0930/p/6581009.html

八:String的New操作创建了几个对象?——两个

我们以String s1=new String("abc");为例

首先当我们的类Class在被ClassLoader加载时,"abc"被作为常量读入,在String Pool(字符串常量池)创建了一个"abc"的实例。

然后,调用到new String("abc")的时候,会在Heap里面复制一个相同的对象。

(1)类加载对一个类只会进行一次。"abc"在类加载时就已经创建并驻留了(如果该类被加载之前已经有"abc"字符串被驻留过则不需要重复创建用于驻留的"abc"实例)。驻留的字符串引用是放在全局共享的字符串常量池中的。[加载时创建一次]

(2)在这段代码后续被运行的时候,"abc"字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象实例复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。[运行时复制一次]

因此,这条语句创建了2个对象。【这两个引用,它们的对象实例是不同的。】

九:String的hashCode的缓存和懒加载初始化

String类中定义了一个私有成员变量——hash,它是一个整数,保存String对象的哈希值,也就是说String类型的对象的哈希值不会重复计算,计算过一次后就保存起来了,以后在被hash时直接取该值即可,无需重新计算

这个值在String对象第一次被调用时进行初始化(懒加载,不是在String对象创建时初始化)。

Java字符串——String深入的更多相关文章

  1. Java字符串String

    Java字符串String 我们知道Java的字符窜是Immutable(不可变)的,一旦创建就不能更改其内容了:平常我们对字符串的操作是最多的,其实对字符串的操作,返回的字符串都是新建的字符串对象, ...

  2. Java字符串String 集合的迭代器

    Java字符串String 我们知道Java的字符窜是Immutable(不可变)的,一旦创建就不能更改其内容了:平常我们对字符串的操作是最多的,其实对字符串的操作,返回的字符串都是新建的字符串对象, ...

  3. java 字符串String

    在 Java 中,字符串被作为 String 类型的对象处理. String 类位于 java.lang 包中.默认情况下,该包被自动导入所有的程序. 创建 String 对象的方法: 只要是双引号标 ...

  4. Java 字符串 String

    什么是Java中的字符串 在 Java 中,字符串被作为 String 类型的对象处理. String 类位于 java.lang 包中.默认情况下,该包被自动导入所有的程序. 创建 String 对 ...

  5. Java字符串String类操作方法详细整理

    关于String类的基本操作,可分为以下几类: 1.基本操作方法 2.字符串比较 3.字符串与其他数据类型之间的转换 4.字符与字符串的查找 5.字符串的截取与拆分 6.字符串的替换与修改 我觉得在整 ...

  6. java 字符串(String)常用技巧及自建方法模块汇总

    1.String类常用方法汇总 (1)删除字符串的头尾空白符 public String trim() (2)从指定位置截取字符串 public String substring(int beginI ...

  7. [Java学习] Java字符串(String)

    从表面上看,字符串就是双引号之间的数据,例如“微学苑”.“http://www.weixueyuan.net”等.在Java中,可以使用下面的方法定义字符串: String stringName = ...

  8. Java字符串(String)

    从表面上看,字符串就是双引号之间的数据,例如“微学苑”.“http://www.weixueyuan.net”等.在Java中,可以使用下面的方法定义字符串:    String stringName ...

  9. 老司机也晕车--java字符串String晕车之旅

    首先声明,有晕车经历的司机请自备药物,String也可能让你怀疑人生! 第一道 开胃菜 请听题!第一道题: String hello="hello world!"; String ...

随机推荐

  1. zzXDL

        Pull requestsIssues Marketplace Explore             Learn Git and GitHub without any code! Using ...

  2. JDOJ 2898 删数问题

    洛谷 P1106 删数问题 https://www.luogu.org/problemnew/show/P1106 JDOJ 2898: 删数问题 https://neooj.com:8082/old ...

  3. pointnet++之scannet/train.py

    1.作者可能把scannet数据集分成了训练集和测试集并处理成了.pickle文件. 2.在代码运行过程中,作者从.pickle文件中读出训练集1201个场景的x.y.z坐标和测试集312个场景的x. ...

  4. ABP 下载源码报错

    ASP.NET Boilerplate 下载地址应该是这个:https://github.com/aspnetboilerplate/aspnetboilerplate/tree/v1.5.2 下载的 ...

  5. Xamarin.Forms移动开发系列1:介绍和安装

    摘要 Xamarin成立于2011年5月16日.Xamarin 是一套基于C#语言的跨平台移动应用开发工具,2016年2月24日被微软正式收购. 前言 很早就已经听说强大的.NET生态中有一个移动开发 ...

  6. 使用maven-resources-plugin插件分环境配置

    一.项目目录结构    二.pom文件中引入maven-resources-plugin插件和相关的标签 <build> <plugins> <plugin> &l ...

  7. Linux record

    1.设置ubuntu密码刚安装好的ubuntu系统,没有root密码,需要用户去手动设置的. sudo passwd root 输入2次密码即可. 2. Linux下is not in the sud ...

  8. Laravel框架中Echo的使用过程

    今天的这篇文章中给大家分享关于laravel框架中echo的使用,文章的内容是一步一步来的,用了16步走完一个过程,过程很清晰,希望可以帮助到有需要的朋友吧.话不多说,直接看内容.官方文档推荐使用 P ...

  9. SQL --------------- 运算符 = 与 in

    in 用于指定查询与where 一块进行使用,可以用来指定一个或多个,和 “ = ” 差不多 语法: select * from 表名 where 字段 in (字段对应的值可以是一个或多个) 建个表 ...

  10. StuQ技能图谱