From cef8058f1c9e0c4b01f4970e4384ec0772b11144 Mon Sep 17 00:00:00 2001 From: itwanger Date: Wed, 1 Mar 2023 22:30:47 +0800 Subject: [PATCH] =?UTF-8?q?Java=E5=A6=82=E4=BD=95=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E5=AD=97=E7=AC=A6=E4=B8=B2=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E7=9B=B8=E7=AD=89=EF=BC=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/string/equals.md | 87 +++++- ...33\351\230\266\344\271\213\350\267\257.md" | 293 ++++++++++++++++++ 2 files changed, 374 insertions(+), 6 deletions(-) diff --git a/docs/string/equals.md b/docs/string/equals.md index b477033db..1545fcf5b 100644 --- a/docs/string/equals.md +++ b/docs/string/equals.md @@ -9,9 +9,10 @@ description: Java程序员进阶之路,小白的零基础Java教程,从入 head: - - meta - name: keywords - content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java字符串,String,equals + content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java字符串,String,equals,java equals,java string 比较,java字符串比较 --- +# 4.8 Java如何判断两个字符串是否相等? “二哥,如何比较两个字符串相等啊?”三妹问。 @@ -53,11 +54,11 @@ public boolean equals(Object obj) { 但实际情况中,有不少类重写了 `.equals()` 方法,因为比较内存地址的要求比较严格,不太符合现实中所有的场景需求。拿 String 类来说,我们在比较字符串的时候,的确只想判断它们俩的内容是相等的就可以了,并不想比较它们俩是不是同一个对象。 -况且,字符串有字符串常量池的概念,本身就推荐使用 `String s = "字符串"` 这种形式来创建字符串对象,而不是通过 new 关键字的方式,因为可以把字符串缓存在字符串常量池中,方便下次使用。 +况且,字符串有[字符串常量池](https://tobebetterjavaer.com/string/constant-pool.html)的概念,本身就推荐使用 `String s = "字符串"` 这种形式来创建字符串对象,而不是通过 new 关键字的方式,因为可以把字符串缓存在字符串常量池中,方便下次使用,不用遇到 new 就在堆上开辟一块新的空间。 “哦,我明白了。”三妹说。 -“那就来看一下 `.equals()` 方法的源码吧。”我说。 +“那就来看一下 String 类的 `.equals()` 方法的源码吧。”我说。 ```java public boolean equals(Object anObject) { @@ -92,7 +93,39 @@ public static boolean equals(byte[] value, byte[] other) { } ``` -我的 JDK 版本是 Java 17,也就是最新的 LTS(长期支持)版本。该版本中,String 类使用字节数组实现的,所以比较两个字符串的内容是否相等时,可以先比较字节数组的长度是否相等,不相等就直接返回 false;否则就遍历两个字符串的字节数组,只有有一个字节不相等,就返回 false。 +这个 JDK 版本是 Java 17,也就是最新的 LTS(长期支持)版本。该版本中,String 类使用字节数组实现的,所以比较两个字符串的内容是否相等时,可以先比较字节数组的长度是否相等,不相等就直接返回 false;否则就遍历两个字符串的字节数组,只有有一个字节不相等,就返回 false。 + +这是 Java 8 中的 equals 方法源码: + +```java +public boolean equals(Object anObject) { + // 判断是否为同一对象 + if (this == anObject) { + return true; + } + // 判断对象是否为 String 类型 + if (anObject instanceof String) { + String anotherString = (String)anObject; + int n = value.length; + // 判断字符串长度是否相等 + if (n == anotherString.value.length) { + char v1[] = value; + char v2[] = anotherString.value; + int i = 0; + // 判断每个字符是否相等 + while (n-- != 0) { + if (v1[i] != v2[i]) + return false; + i++; + } + return true; + } + } + return false; +} +``` + +JDK 8 比 JDK 17 更容易懂一些:首先判断两个对象是否为同一个对象,如果是,则返回 true。接着,判断对象是否为 String 类型,如果不是,则返回 false。如果对象为 String 类型,则比较两个字符串的长度是否相等,如果长度不相等,则返回 false。如果长度相等,则逐个比较每个字符是否相等,如果都相等,则返回 true,否则返回 false。 “嗯,二哥,这段源码不难理解。”三妹自信地说。 @@ -106,8 +139,7 @@ new String("小萝莉").equals("小萝莉") “输出什么呢?”我问。 -“`.equals()` 比较的是两个字符串对象的内容是否相等,所以结果为 true。”三妹答。 - +“`.equals()` 比较的是两个字符串对象的内容是否相等,所以结果为 true。”三妹不假思索地答到。 第二题: @@ -142,6 +174,8 @@ new String("小萝莉") == new String("小萝莉") “由于‘小’和‘萝莉’都在字符串常量池,所以编译器在遇到‘+’操作符的时候将其自动优化为“小萝莉”,所以返回 true。” +PS:至于为什么,查看这篇[String、StringBuilder、StringBuffer](https://tobebetterjavaer.com/string/builder-buffer.html) + 第六题: ```java @@ -150,6 +184,8 @@ new String("小萝莉").intern() == "小萝莉" “`new String("小萝莉")` 在执行的时候,会先在字符串常量池中创建对象,然后再在堆中创建对象;执行 `intern()` 方法的时候发现字符串常量池中已经有了‘小萝莉’这个对象,所以就直接返回字符串常量池中的对象引用了,那再与字符串常量池中的‘小萝莉’比较,当然会返回 true 了。”三妹说。 +PS:[intern](https://tobebetterjavaer.com/string/intern.html) 方法我们之前已经深究过了。 + 哇,不得不说,三妹前几节的字符串相关内容都完全学会了呀! “三妹,哥再给你补充一点。”我说。 @@ -220,6 +256,45 @@ public boolean contentEquals(CharSequence cs) { 从源码上可以看得出,如果 cs 是 StringBuffer,该方法还会进行同步,非常的智能化;如果是 String 的话,其实调用的还是 `equals()` 方法。当然了,这也就意味着使用该方法进行比较的时候,多出来了很多步骤,性能上有些损失。 +同样来看一下 JDK 8 的源码: + +```java +public boolean contentEquals(CharSequence cs) { + // argument can be any CharSequence implementation + if (cs.length() != value.length) { + return false; + } + // Argument is a StringBuffer, StringBuilder or String + if (cs instanceof AbstractStringBuilder) { + char v1[] = value; + char v2[] = ((AbstractStringBuilder)cs).getValue(); + int i = 0; + int n = value.length; + while (n-- != 0) { + if (v1[i] != v2[i]) + return false; + i++; + } + return true; + } + // Argument is a String + if (cs.equals(this)) + return true; + // Argument is a non-String, non-AbstractStringBuilder CharSequence + char v1[] = value; + int i = 0; + int n = value.length; + while (n-- != 0) { + if (v1[i] != cs.charAt(i)) + return false; + i++; + } + return true; +} +``` + +同样更容易理解一些:首先判断参数长度是否相等,不相等则返回 false。如果参数是 AbstractStringBuilder 的实例,则取出其 char 数组,遍历比较两个 char 数组的每个元素是否相等。如果参数是 String 的实例,则直接调用 equals 方法比较两个字符串是否相等。如果参数是其他实现了 CharSequence 接口的对象,则遍历比较两个对象的每个字符是否相等。 + “是的,总体上感觉还是 `Objects.equals()` 比较舒服。”三妹的眼睛是雪亮的,发现了这个方法的优点。 --- diff --git "a/\344\272\214\345\223\245\347\232\204 Java \350\277\233\351\230\266\344\271\213\350\267\257.md" "b/\344\272\214\345\223\245\347\232\204 Java \350\277\233\351\230\266\344\271\213\350\267\257.md" index cbe6e18d6..1e9dc9505 100644 --- "a/\344\272\214\345\223\245\347\232\204 Java \350\277\233\351\230\266\344\271\213\350\267\257.md" +++ "b/\344\272\214\345\223\245\347\232\204 Java \350\277\233\351\230\266\344\271\213\350\267\257.md" @@ -4178,4 +4178,297 @@ void expandCapacity(int minimumCapacity) { 微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。 +![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png) + +## 4.8 Java如何判断两个字符串是否相等? + +“二哥,如何比较两个字符串相等啊?”三妹问。 + +“这个问题看似简单,却在 Stack Overflow 上有超过 370 万+的访问量。”我说,“这个问题也可以引申为 `.equals()` 和 ‘==’ 操作符有什么区别。” + +- “==”操作符用于比较两个对象的地址是否相等。 +- `.equals()` 方法用于比较两个对象的内容是否相等。 + +“不是很理解。”三妹感到很困惑。 + +“我来举个不恰当又很恰当的例子,一看你就明白了,三妹。” + +有一对双胞胎,姐姐叫阿丽塔,妹妹叫洛丽塔。我们普通人可能完全无法分辨谁是姐姐谁是妹妹,可她们的妈妈却可以轻而易举地辨认出。 + +![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string/equals-01.png) + + +`.equals()` 就好像我们普通人,看见阿丽塔以为是洛丽塔,看见洛丽塔以为是阿丽塔,看起来一样就觉得她们是同一个人;“==”操作符就好像她们的妈妈,要求更严格,观察更细致,一眼就能分辨出谁是姐姐谁是妹妹。 + +```java +String alita = new String("小萝莉"); +String luolita = new String("小萝莉"); + +System.out.println(alita.equals(luolita)); // true +System.out.println(alita == luolita); // false +``` + +就上面这段代码来说,`.equals()` 输出的结果为 true,而“==”操作符输出的结果为 false——前者要求内容相等就可以,后者要求必须是同一个对象。 + +“三妹,之前已经学过了,Java 的所有类都默认地继承 Object 这个超类,该类有一个名为 `.equals()` 的方法。”一边说,我一边打开了 Object 类的源码。 + +```java +public boolean equals(Object obj) { + return (this == obj); +} +``` + +你看,Object 类的 `.equals()` 方法默认采用的是“==”操作符进行比较。假如子类没有重写该方法的话,那么“==”操作符和 `.equals()` 方法的功效就完全一样——比较两个对象的内存地址是否相等。 + +但实际情况中,有不少类重写了 `.equals()` 方法,因为比较内存地址的要求比较严格,不太符合现实中所有的场景需求。拿 String 类来说,我们在比较字符串的时候,的确只想判断它们俩的内容是相等的就可以了,并不想比较它们俩是不是同一个对象。 + +况且,字符串有[字符串常量池](https://tobebetterjavaer.com/string/constant-pool.html)的概念,本身就推荐使用 `String s = "字符串"` 这种形式来创建字符串对象,而不是通过 new 关键字的方式,因为可以把字符串缓存在字符串常量池中,方便下次使用,不用遇到 new 就在堆上开辟一块新的空间。 + +“哦,我明白了。”三妹说。 + +“那就来看一下 String 类的 `.equals()` 方法的源码吧。”我说。 + +```java +public boolean equals(Object anObject) { + if (this == anObject) { + return true; + } + if (anObject instanceof String) { + String aString = (String)anObject; + if (coder() == aString.coder()) { + return isLatin1() ? StringLatin1.equals(value, aString.value) + : StringUTF16.equals(value, aString.value); + } + } + return false; +} +``` + +首先,如果两个字符串对象的可以“==”,那就直接返回 true 了,因为这种情况下,字符串内容是必然相等的。否则就按照字符编码进行比较,分为 UTF16 和 Latin1,差别不是很大,就拿 Latin1 的来说吧。 + +```java +@HotSpotIntrinsicCandidate +public static boolean equals(byte[] value, byte[] other) { + if (value.length == other.length) { + for (int i = 0; i < value.length; i++) { + if (value[i] != other[i]) { + return false; + } + } + return true; + } + return false; +} +``` + +这个 JDK 版本是 Java 17,也就是最新的 LTS(长期支持)版本。该版本中,String 类使用字节数组实现的,所以比较两个字符串的内容是否相等时,可以先比较字节数组的长度是否相等,不相等就直接返回 false;否则就遍历两个字符串的字节数组,只有有一个字节不相等,就返回 false。 + +这是 Java 8 中的 equals 方法源码: + +```java +public boolean equals(Object anObject) { + // 判断是否为同一对象 + if (this == anObject) { + return true; + } + // 判断对象是否为 String 类型 + if (anObject instanceof String) { + String anotherString = (String)anObject; + int n = value.length; + // 判断字符串长度是否相等 + if (n == anotherString.value.length) { + char v1[] = value; + char v2[] = anotherString.value; + int i = 0; + // 判断每个字符是否相等 + while (n-- != 0) { + if (v1[i] != v2[i]) + return false; + i++; + } + return true; + } + } + return false; +} +``` + +JDK 8 比 JDK 17 更容易懂一些:首先判断两个对象是否为同一个对象,如果是,则返回 true。接着,判断对象是否为 String 类型,如果不是,则返回 false。如果对象为 String 类型,则比较两个字符串的长度是否相等,如果长度不相等,则返回 false。如果长度相等,则逐个比较每个字符是否相等,如果都相等,则返回 true,否则返回 false。 + +“嗯,二哥,这段源码不难理解。”三妹自信地说。 + +“那出几道题考考你吧!”我说。 + +第一题: + +```java +new String("小萝莉").equals("小萝莉") +``` + +“输出什么呢?”我问。 + +“`.equals()` 比较的是两个字符串对象的内容是否相等,所以结果为 true。”三妹不假思索地答到。 + + +第二题: + +```java +new String("小萝莉") == "小萝莉" +``` + +“==操作符左侧的是在堆中创建的对象,右侧是在字符串常量池中的对象,尽管内容相同,但内存地址不同,所以返回 false。”三妹答。 + +第三题: + +```java +new String("小萝莉") == new String("小萝莉") +``` + +“new 出来的对象肯定是完全不同的内存地址,所以返回 false。”三妹答。 + +第四题: + +```java +"小萝莉" == "小萝莉" +``` + +“字符串常量池中只会有一个相同内容的对象,所以返回 true。”三妹答。 + +第五题: + +```java +"小萝莉" == "小" + "萝莉" +``` + +“由于‘小’和‘萝莉’都在字符串常量池,所以编译器在遇到‘+’操作符的时候将其自动优化为“小萝莉”,所以返回 true。” + +PS:至于为什么,查看这篇[String、StringBuilder、StringBuffer](https://tobebetterjavaer.com/string/builder-buffer.html) + +第六题: + +```java +new String("小萝莉").intern() == "小萝莉" +``` + +“`new String("小萝莉")` 在执行的时候,会先在字符串常量池中创建对象,然后再在堆中创建对象;执行 `intern()` 方法的时候发现字符串常量池中已经有了‘小萝莉’这个对象,所以就直接返回字符串常量池中的对象引用了,那再与字符串常量池中的‘小萝莉’比较,当然会返回 true 了。”三妹说。 + +PS:[intern](https://tobebetterjavaer.com/string/intern.html) 方法我们之前已经深究过了。 + +哇,不得不说,三妹前几节的字符串相关内容都完全学会了呀! + +“三妹,哥再给你补充一点。”我说。 + +“如果要进行两个字符串对象的内容比较,除了 `.equals()` 方法,还有其他两个可选的方案。” + +1)`Objects.equals()` + +`Objects.equals()` 这个静态方法的优势在于不需要在调用之前判空。 + +```java +public static boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); +} +``` + +如果直接使用 `a.equals(b)`,则需要在调用之前对 a 进行判空,否则可能会抛出空指针 `java.lang.NullPointerException`。`Objects.equals()` 用起来就完全没有这个担心。 + +```java +Objects.equals("小萝莉", new String("小" + "萝莉")) // --> true +Objects.equals(null, new String("小" + "萝莉")); // --> false +Objects.equals(null, null) // --> true + +String a = null; +a.equals(new String("小" + "萝莉")); // throw exception +``` + +2)String 类的 `.contentEquals()` + +`.contentEquals()` 的优势在于可以将字符串与任何的字符序列(StringBuffer、StringBuilder、String、CharSequence)进行比较。 + +```java +public boolean contentEquals(CharSequence cs) { + // Argument is a StringBuffer, StringBuilder + if (cs instanceof AbstractStringBuilder) { + if (cs instanceof StringBuffer) { + synchronized(cs) { + return nonSyncContentEquals((AbstractStringBuilder)cs); + } + } else { + return nonSyncContentEquals((AbstractStringBuilder)cs); + } + } + // Argument is a String + if (cs instanceof String) { + return equals(cs); + } + // Argument is a generic CharSequence + int n = cs.length(); + if (n != length()) { + return false; + } + byte[] val = this.value; + if (isLatin1()) { + for (int i = 0; i < n; i++) { + if ((val[i] & 0xff) != cs.charAt(i)) { + return false; + } + } + } else { + if (!StringUTF16.contentEquals(val, cs, n)) { + return false; + } + } + return true; +} +``` + +从源码上可以看得出,如果 cs 是 StringBuffer,该方法还会进行同步,非常的智能化;如果是 String 的话,其实调用的还是 `equals()` 方法。当然了,这也就意味着使用该方法进行比较的时候,多出来了很多步骤,性能上有些损失。 + +同样来看一下 JDK 8 的源码: + +```java +public boolean contentEquals(CharSequence cs) { + // argument can be any CharSequence implementation + if (cs.length() != value.length) { + return false; + } + // Argument is a StringBuffer, StringBuilder or String + if (cs instanceof AbstractStringBuilder) { + char v1[] = value; + char v2[] = ((AbstractStringBuilder)cs).getValue(); + int i = 0; + int n = value.length; + while (n-- != 0) { + if (v1[i] != v2[i]) + return false; + i++; + } + return true; + } + // Argument is a String + if (cs.equals(this)) + return true; + // Argument is a non-String, non-AbstractStringBuilder CharSequence + char v1[] = value; + int i = 0; + int n = value.length; + while (n-- != 0) { + if (v1[i] != cs.charAt(i)) + return false; + i++; + } + return true; +} +``` + +同样更容易理解一些:首先判断参数长度是否相等,不相等则返回 false。如果参数是 AbstractStringBuilder 的实例,则取出其 char 数组,遍历比较两个 char 数组的每个元素是否相等。如果参数是 String 的实例,则直接调用 equals 方法比较两个字符串是否相等。如果参数是其他实现了 CharSequence 接口的对象,则遍历比较两个对象的每个字符是否相等。 + +“是的,总体上感觉还是 `Objects.equals()` 比较舒服。”三妹的眼睛是雪亮的,发现了这个方法的优点。 + +--- + +最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) + +微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。 + ![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png) \ No newline at end of file -- GitLab