真的懂Java的String吗?

开发 后端
String 被 final 修饰,说明 String 类绝不可能被继承了,也就是说任何对 String 的操作方法,都不会被继承覆写,即可保证双亲委派机制,保证基类的安全性。

[[391738]]

本文转载自微信公众号「学习Java的小姐姐」,作者学习Java的小姐姐0618。转载本文请联系学习Java的小姐姐公众号。

1.String的特性

1.1不变性

我们常常听人说,HashMap 的 key 建议使用不可变类,比如说 String 这种不可变类。这里说不可变指的是类值一旦被初始化,就不能再被改变了,如果被修改,将会是新的类,我们写个demo 来演示一下。

  1. public class test {     
  2.   public static void main(String[] args){ 
  3.         String str="hello"
  4.         str=str+"world"
  5.     } 

从代码上来看,s 的值好像被修改了,但从 debug 的日志来看,其实是 s 的内存地址已经被修了,也就说 s =“world” 这个看似简单的赋值,其实已经把 s 的引用指向了新的 String,debug 截图显示内存地址已经被修改,两张截图如下,我们可以看到标红的地址值已经修改了。

用示意图来表示堆内存,即见下图。

我们可以看下str的地址已经改了,说了生成了两个字符串,String类的官方注释为Strings are constant; their values cannot be changed after they are created. 简单翻译下为字符串是常量;它们的值在创建后不能更改。

下面为String的相关代码,如下代码,我们可以看到:

1. String 被 final 修饰,说明 String 类绝不可能被继承了,也就是说任何对 String 的操作方法,都不会被继承覆写,即可保证双亲委派机制,保证基类的安全性。

2. String 中保存数据的是一个 char 的数组 value。我们发现 value 也是被 final 修饰的,也就是说 value 一旦被赋值,内存地址是绝对无法修改的,而且 value 的权限是 private 的,外部绝对访问不到,String没有开放出可以对 value 进行赋值的方法,所以说 value 一旦产生,内存地址就根本无法被修改。

  1. //char类型的final数组 
  2.     private final char value[];     
  3.      
  4.     //hash值 
  5.     private int hash;  
  6.  
  7.     private static final long serialVersionUID = -6849794470754667710L; 

1.2相等判断

相等判断逻辑写的很清楚明了,如果有人问如何判断两者是否相等时,我们可以从两者的底层结构出发,这样可以迅速想到一种贴合实际的思路和方法,就像 String 底层的数据结构是 char 的数组一样,判断相等时,就挨个比较 char 数组中的字符是否相等即可。(这里先挖个坑,携程问过类似题目)

  1. public boolean equals(Object anObject) {               
  2.        //如果地址相等,则直接返回true 
  3.        if (this == anObject) {      
  4.  
  5.              return true
  6.         }       
  7.         //如果为String字符串,则进行下面的逻辑判断 
  8.         if (anObject instanceof String) {           
  9.           //将对象转化为String 
  10.             String anotherString = (String)anObject;       
  11.             //获取当前值的长度 
  12.             int n = value.length;             
  13.             //先比较长度是否相等,如果长度不相等,这两个肯定不相等 
  14.             if (n == anotherString.value.length) {              
  15.                    char v1[] = value;             
  16.                    char v2[] = anotherString.value;                int i = 0;                //while循环挨个比较每个char 
  17.                     while (n-- != 0) {                 
  18.                         if (v1[i] != v2[i])                
  19.                              return false
  20.                         i++; 
  21.                     }             
  22.                 return true
  23.             } 
  24.         }      
  25.            return false
  26.     } 

相等逻辑的流程图如下,我们可以看到整个流程还是很清楚的。

1.3替换操作

替换在平时工作中也经常使用,主要有 replace 替换所有字符、replaceAll 批量替换字符串、replaceFirst这三种场景。

下面写了一个 demo 演示一下三种场景:

  1. public static void main(String[] args) { 
  2.         String str = "hello word !!"
  3.         System.out.println("替换之前 :" + str); 
  4.         str = str.replace('l''d'); 
  5.         System.out.println("替换所有字符 :" + str); 
  6.         str = str.replaceAll("d""l"); 
  7.         System.out.println("替换全部 :" + str); 
  8.         str = str.replaceFirst("l"""); 
  9.         System.out.println("替换第一个 l :" + str); 
  10.     } 

输出的结果是:

这边要注意一点是replace和replaceAll的区别,不是替换和替换所有的区别哦。

而是replaceAll支持正则表达式,因此会对参数进行解析(两个参数均是),如replaceAll("\\d", "*"),而replace则不会,replace("\\d","*")就是替换"\\d"的字符串,而不会解析为正则。

1.4 intern方法

String.intern() 是一个 Native 方法,即是c和c++与底层交互的代码,它的作用(在JDK1.6和1.7操作不同)是:

如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则直接返回常量池中该字符串的引用;

如果没有, 那么在jdk1.6中,将此String对象添加到常量池中,然后返回这个String对象的引用(此时引用的串在常量池)。

在jdk1.7中,放入一个引用,指向堆中的String对象的地址,返回这个引用地址(此时引用的串在堆)。

  1. public native String intern(); 

如果看上面看不懂,我们来看下一下具体的例子,并来分析下。

  1. public static void main(String[] args) { 
  2.         String s1 = new String("学习Java的小姐姐"); 
  3.         s1.intern(); 
  4.         String s2 = "学习Java的小姐姐"
  5.         System.out.println(s1 == s2); 
  6.  
  7.         String s3 = new String("学习Java的小姐姐") + new String("test"); 
  8.         s3.intern(); 
  9.         String s4 = "学习Java的小姐姐test"
  10.         System.out.println(s3 == s4); 
  11.  
  12.     } 

我们来看下结果,实际的打印信息如下。

为什么显示这样的结果,我们来看下。所以在 jdk7 的版本中,字符串常量池已经从方法区移到正常的堆 区域了。

  • 第一个false: 第一句代码String s1 = new String("学习Java的小姐姐");生成了2个对象。常量池中的“学习Java的小姐姐” 和堆中的字符串对象。s1.intern(); 这一句是 s1 对象去常量池中寻找后,发现 “学习Java的小姐姐” 已经在常量池里了。接下来String s2 = "学习Java的小姐姐"; 这句代码是生成一个 s2的引用指向常量池中的“学习Java的小姐姐”对象。结果就是 s 和 s2 的引用地址明显不同,所以打印结果是false。
  • 第二个true:先看 s3和s4字符串。String s3 = new String("学习Java的小姐姐") + new String("test");,这句代码中现在生成了3个对象,是字符串常量池中的“学习Java的小姐姐” ,"test"和堆 中的 s3引用指向的对象。此时s3引用对象内容是”学习Java的小姐姐test”,但此时常量池中是没有 “学习Java的小姐姐test”对象的,接下来s3.intern();这一句代码,是将 s3中的“学习Java的小姐姐test”字符串放入 String 常量池中,因为此时常量池中不存在“学习Java的小姐姐test”字符串,常量池不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。也就是说引用地址是相同的。最后String s4 = "学习Java的小姐姐test"; 这句代码中”学习Java的小姐姐test”是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。

我们再看下,如果把上面的两行代码调整下位置,打印结果是不是不同。

  1. public static void main(String[] args) { 
  2.         String s1 = new String("学习Java的小姐姐"); 
  3.         String s2 = "学习Java的小姐姐"
  4.         s1.intern(); 
  5.         System.out.println(s1 == s2); 
  6.  
  7.         String s3 = new String("学习Java的小姐姐") + new String("test"); 
  8.         String s4 = "学习Java的小姐姐test"
  9.         s3.intern(); 
  10.         System.out.println(s3 == s4); 
  11.  
  12.     } 

  • 第一个false: s1 和 s2 代码中,s1.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("学习Java的小姐姐");的时候已经生成“学习Java的小姐姐”对象了。下边的s2声明都是直接从常量池中取地址引用的。s 和 s2 的引用地址是不会相等的。
  • 第二个false:与上面唯一的区别在于 s3.intern(); 的顺序是放在String s4 = "学习Java的小姐姐test";后了。这样,首先执行String s4 = "学习Java的小姐姐test";声明 s4 的时候常量池中是不存在“学习Java的小姐姐test”对象的,执行完毕后,“学习Java的小姐姐test“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“学习Java的小姐姐test”对象已经存在了,因此 s3 和 s4 的引用是不同的。

2. String、StringBuilder和StringBuffer

2.1 继承结构

2.2 主要区别

1)String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列。

2)执行速度StringBuilder > StringBuffer > String。

3)StringBuilder是非线程安全的,StringBuffer是线程安全的。

责任编辑:武晓燕 来源: 学习Java的小姐姐
相关推荐

2019-05-13 14:17:06

抓包Web安全漏洞

2019-10-18 09:50:47

网络分层模型网络协议

2019-09-15 10:38:28

网络分层模型

2023-11-29 08:03:05

2021-08-30 15:41:13

Kafka运维数据

2020-03-29 08:27:05

Promise异步编程前端

2021-04-07 17:06:55

String Final存储

2021-01-22 07:48:07

JavaScript 高阶函数闭包

2021-11-08 10:00:19

require前端模块

2021-07-21 10:10:14

require前端代码

2024-10-16 17:10:41

2018-07-17 16:26:17

大数据营销消费者

2017-11-07 12:35:53

比特币区块链虚拟货币

2023-09-17 22:46:50

2017-05-31 08:45:03

2017-06-27 13:50:37

数据分析Session

2017-08-07 08:32:58

泄密网盘存储

2020-06-04 14:15:55

Java中BigDecimal函数

2023-11-01 13:48:00

反射java

2018-09-29 15:34:34

JavaList接口
点赞
收藏

51CTO技术栈公众号