关于java:真的懂Java的String吗

28次阅读

共计 5181 个字符,预计需要花费 13 分钟才能阅读完成。

1.String 的个性

1.1 不变性

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

public class test {public static void main(String[] args){
        String str="hello";
        str=str+"world";
    }
}
复制代码

从代码上来看,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 一旦产生,内存地址就根本无法被批改。
 /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
复制代码

1.2 相等判断

相等判断逻辑写的很分明明了,如果有人问如何判断两者是否相等时,咱们能够从两者的底层构造登程,这样能够迅速想到一种贴合理论的思路和办法,就像 String 底层的数据结构是 char 的数组一样,判断相等时,就挨个比拟 char 数组中的字符是否相等即可。

(这里先挖个坑,携程问过相似题目)

 public boolean equals(Object anObject) {
       // 如果地址相等,则间接返回 true       
       if (this == anObject) {return true;}
        // 如果为 String 字符串,则进行上面的逻辑判断
        if (anObject instanceof String) {
            // 将对象转化为 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 循环挨个比拟每个 char
                while (n-- != 0) {if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
复制代码

相等逻辑的流程图如下,咱们能够看到整个流程还是很分明的。

1.3 替换操作

替换在平时工作中也常常应用,次要有 replace 替换所有字符、replaceAll 批量替换字符串、replaceFirst 这三种场景。
上面写了一个 demo 演示一下三种场景:

 public static void main(String[] args) {
        String str = "hello word !!";
        System.out.println("替换之前 :" + str);
        str = str.replace('l', 'd');
        System.out.println("替换所有字符 :" + str);
        str = str.replaceAll("d", "l");
        System.out.println("替换全副 :" + str);
        str = str.replaceFirst("l", "");
        System.out.println("替换第一个 l :" + str);
    }
复制代码

输入的后果是:

这边要留神一点是 replacereplaceAll的区别,不是替换和替换所有的区别 哦。

而是 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 对象的地址,返回这个援用地址(此时援用的串在堆)。

 /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */    
public native String intern();
复制代码

如果看下面看不懂,咱们来看下一下具体的例子,并来剖析下。

public static void main(String[] args) {String s1 = new String("学习 Java 的小姐姐");
        s1.intern();
        String s2 = "学习 Java 的小姐姐";
        System.out.println(s1 == s2);

        String s3 = new String("学习 Java 的小姐姐") + new String("test");
        s3.intern();
        String s4 = "学习 Java 的小姐姐 test";
        System.out.println(s3 == s4);

    }
复制代码

咱们来看下后果,理论的打印信息如下。

为什么显示这样的后果,咱们来看下。所以在 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。

咱们再看下,如果把下面的两行代码调整下地位,打印后果是不是不同。

 public static void main(String[] args) {String s1 = new String("学习 Java 的小姐姐");
        String s2 = "学习 Java 的小姐姐";
        s1.intern();
        System.out.println(s1 == s2);

        String s3 = new String("学习 Java 的小姐姐") + new String("test");
        String s4 = "学习 Java 的小姐姐 test";
        s3.intern();
        System.out.println(s3 == s4);

    }
复制代码

第一个 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 的援用是不同的。

  1. String、StringBuilder 和 StringBuffer

2.1 继承构造


2.2 次要区别

1)String 是不可变字符序列,StringBuilder 和 StringBuffer 是可变字符序列。
2)执行速度 StringBuilder > StringBuffer > String。
3)StringBuilder 是非线程平安的,StringBuffer 是线程平安的。

参考:《2020 最新 Java 根底精讲视频教程和学习路线!》
链接:https://juejin.cn/post/694526…

正文完
 0