zoukankan      html  css  js  c++  java
  • Java入门系列之字符串特性(二)

    前言

    上一节我们讲解到字符串本质上就是字符数组,同时详细讲解了字符串判断相等需要注意的地方,本节我们来深入探讨字符串特性,下面我们一起来看看。

    不可变性

    我们依然借助初始化字符串的方式来探讨字符串的不可变性,如下:

    String str = "Jeffcky";
    System.out.println(str);

    上述我们通过字面量的方式来创建字符串,接下来我们对字符串str进行如下操作:

    String str = "Jeffcky";
    str.substring(0,3).concat("wang").toLowerCase().trim(); 
    System.out.println(str);

    我们看到针对str字符串进行截取、连接、小写等操作后,字符串的值依然未发生改变,这就是字符串的不可变性,我们通过查看任意一个对字符串操作的方法,比如concat方法源码:

    public String concat(String str) {
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            int len = value.length;
            char buf[] = Arrays.copyOf(value, len + otherLen);
            str.getChars(buf, len);
            return new String(buf, true);
    }

    通过查看concat源码得出:原始的str值永远都没有发生改变,它的值只是被复制,然后将我们连接的文本添加到复制的副本里,最后返回一个新的String。所以到这里我们知道针对字符串的操作都是复制一份字符串,然后对复制后的字符串进行操作,最终返回一个新的String对象。

    字符串池

    我们再来看看上一节所给出的代码示例,如下:

    public class Main {
        public static void main(String[] args) {
            String str1 = "Jeffcky";
            String str2 = "Jeffcky";
            System.out.println(str1 == str2);
            System.out.println(str1.equals(str2));
        }
    }

    当我们实例化一个String时(在本例中为Jeffcky)保存在Java堆内存(用于所有Java对象的动态内存分配)中。虽然在这个例子中我们有两个不同的引用变量,但它们都只是指Java Heap Memory中的同一内存位置,虽然看起来有两个不同的String对象,但实际上只有一个,而str2永远不会被实例化为对象,而是在内存中分配对应于str1的对象,这是因为Java针对字符串进行了优化处理,每次要实例化此类String对象时,都会将要添加到堆内存的值与先前添加的值进行比较,如果值已存在,则不初始化对象,并将值分配给引用变量,这些值保存在名叫“字符串池”中,该字符串池包含所有文字字符串值,当然我们可以通过new运算符绕过这种情况。

    为什么字符串是不可变或final呢? 

    上述我们通过例子说明了字符串的不可变性特性,那么为什么字符串是不可变的呢?可以参考知乎回答:《https://www.zhihu.com/question/31345592》。我认为主要在于有效共享对象,节省内存空间。当程序运行时,创建的String实例的数量也会增长,如果不缓存String常量,堆空间中会有大量的String,占用内存空间,所以String对象被创建后缓存在字符串池中,若缓存的字符串被多个客户端共享,此时一个客户端的操作修改了字符串则影响到其他客户端,因此通过字符串的不可变性来规避这种风险,同时通过缓存和共享字符串常量,JVM为Java应用程序节省内存。

    有了字符串不可变性,可以很安全的被多线程所共享,我们不用担心线程同步问题,确保线程安全。

    有了字符串不可变性,可以很好的使用比如HashMap,我们能正确检索到存储到HashMap中的对象,若字符串可变且在插入到HashMap后并修改了字符串内容,此时将会出现丢失对应字符串所映射的对象。

    有了字符串不可变性,此时会缓存字符串哈希码,所以每次调用字符串的hashcode方法时都不用计算,使得在HashMap中使用键非常快。

    其他等等......

    接下来我们一起来通过源码的方式来看看String的实现,如下:

    public class Main {
        public static void main(String[] args) {
            char a[] = {'j', 'e', 'f', 'f', 'c', 'k', 'y'};
            String str = new String(a);
            System.out.println(str);
        }
    }

    我们通过字符数组创建字符串的方式去查看源码,如下:

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    
        /**
         * Initializes a newly created {@code String} object so that it represents
         * an empty character sequence.  Note that use of this constructor is
         * unnecessary since Strings are immutable.
         */
        public String() {
            this.value = new char[0];
        }
    
        /**
         * Allocates a new {@code String} so that it represents the sequence of
         * characters currently contained in the character array argument. The
         * contents of the character array are copied; subsequent modification of
         * the character array does not affect the newly created string.
         *
         * @param  value
         *         The initial value of the string
         */
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
        
        ...
    }

    我们看到字符串对象定义为final(当前我们还未学到final,我们只需要知道通过final关键字修饰说明该类不可继承),网上有很多例子说字符串对象通过final关键字修饰,说明字符串不可变,其实这种说法是不严谨且错误的。String通过final关键字修饰的原因在于:确保不能通过扩展和覆盖行为来破坏String类的不可变性而非说明字符串不可变。比如,如下例子:

    public class Main {
        public static void main(String[] args) {
            String str1 = "Jeffcky";
            String str2 = "Jeffcky".toUpperCase();
            System.out.println(str1);
            System.out.println(str2);
        }
    }

    现在字符串str2为"Jeffcky".toUpperCase(),我们将同一个对象修改为“JEFFCKY”,如果修改了字符串变量,其他字符串变量也将自动受到影响,比如str1也将是"JEFFCKY",很显然是不可取的。

    总结

    本文我们详细介绍了字符串的不可变、字符串池特性,同时解释了字符串为何不可变,以及说明字符串类定义为final,并不是说明其不可变,只是为了不允许通过扩展或覆盖来破坏字符串的不可变性。

  • 相关阅读:
    django include
    @RequestParam 传值乱码问题
    View Merge 在安全控制上的变化,是 BUG 还是增强 ?
    postMessage实现跨域iframe自适应
    java.lang.IllegalStateException: Service id not legal hostname
    【以前的空间】 单调队列系列
    【以前的空间】 单调队列系列
    【以前的空间】 单调队列系列
    JAVA线程池shutdown和shutdownNow的区别
    JAVA线程池shutdown和shutdownNow的区别
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/11437019.html
Copyright © 2011-2022 走看看