Java----谈谈String

2018-10-14  本文已影响8人  海盗的帽子

csdn
个人博客

一.简介

String 是 Java 中使用得最频繁的一个类了,不管是作为开发者的业务使用,还是一些系统级别的字符使用, String 都发挥着重要的作用。又因 String 是不可变的、final的 且 Java 在运行时也保存了一个字符串池(String pool) ,就使得 String 变得很特殊。

二.==/equals

String 对象的创建有两种方式:

    String strComplier = "A";
    String strNew = new String("A");

虽然同样是创建字符串 "A" ,但是这两种方式在内存分配上是由区别的。

1.String strComplier = "A";

Java 程序在运行的时候会维护者一个常量池,编译期生成的各种字面量和符号引用会在类加载后进入方法区的运行时常量池。对于 上述这种实现字符串的方式就可以在编译的时候确定字符串的内容,因此这一行生成的内存结构就如下图。


image.png

不严谨的讲:虚拟机栈中的 strComplier 存储的就是 A 在常量池中的地址。

2. String strNew = new String("A");

因为使用的 new 的方式,所以这句代码只有的运行的时候才能确定字符串的内容。而对于 new 关键字,java 是将对象的实例数据存放堆上的,但是又因 String 常量池的存在,因此实际上在堆上的 String 对象的数据又指向了字符串常量池。

image.png

不严谨的讲:虚拟机栈中的 strNew 存储的就是 strNew 这个对象在堆内存的地址,而 strNew 中的字符串数据又指向了常量池中的 A .

3.==/equals

有了前面的知识基础之后,对于字符串这两个比较操作就十分简单了。

(1).==

== 比较的两个对象的引用是否相等,也就是说比较两个地址是否相等,显然对于下面的比较。

String a = "A";
String a1 = "A";
System.out.println(a1 == a); //指向的都是 A 的地址,地址相同,返回的是 true
  String a = new String("A");
  String a1 = new String("A");
  System.out.println(a==a1);//分别指向的是在堆内存上的对象的地址,地址不同,返回的是 false
   String a = "A";
   String a1 = new String("A");
   System.out.println(a == a1);//一个指向常量池,一个指向堆,返回 false 
   String a = "A";
   String a1 = a; //编译的时候不能确定
   System.out.println(a == a1);//一个指向常量池,一个指向堆,返回 false 

通过这个例子可以看出实际上对于 == 的比较,只要在编译的时候能够确定的,都是相同 的。

        String a = "A1";
        String a1 = "A" +1;
        System.out.println(a == a1);//true

        String a = "A1";
        String a1 = a +1;
        System.out.println(a == a1);//false
        
        String a = "A1";
        final String a1 = a + 1;
        System.out.println(a == a1);// final 修饰的在 a1 在编译的时候就能确定下来

        private static String get1() {
            return "1";
        }
        String a = "A";
        final String a1 = get1();
        String a2 = "a" + a1;
        System.out.println(a == a2); //使用了 方法,只能在运行的时候确定,所以返回false 
(2)equals
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;
    }

对于 equals 的比较:

      String stringCompiler = "A";
      String stringNew = new String("A");
      StringBuilder stringBuilder = new StringBuilder("A");
      StringBuffer stringBuffer = new StringBuffer("A")
      System.out.println(stringCompiler.equals(stringNew));  // true
      System.out.println(stringCompiler.equals(stringBuilder));  // false
      System.out.println(stringNew.equals(stringBuffer));   // false
(3)引申
  String strNew = new String("A");

对于这行代码,前面说过会在堆保存对象的实例数据,然后在常量池保存了 “A” 这个常量。所以这一句实际涉及了两个 String 对象。

三.final

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {}

总所周知,String 是 final ,也就是String 是不可变的。即一个 String 对象创建之后所有对它修改后的字符串都是新生成的 String 对象。

String 设计成 final 主要有下的原因:

使用 StringBuilder 和 StringBuffer 拼接字符串

前面说过 String 是final 类的,因此对于字符串的拼接实际上就是创建了新的对象。

        String str = "111";
        str += "222";
        str += "333";
        System.out.println(str);

编译器每次碰到 ”+=” 的时候,会 new 一个 StringBuilder 出来,接着调用 append 方法,在调用 toString 方法,生成新字符串。因此对于字符串的拼接,应该直接使用 StringBuilder 或者 StringBuffer。虽然 这两类也是 final 但是他们在内部 维护了父类一个可变的 byte 数组,每次 append 的时候就往 byte 数组里面放字符.

StringBuffer 和 StringBuilder用法一模一样,唯一的区别只是 StringBuffer 是线程安全的,它对所有方法都做了同步,StringBuilder 是线程非安全的,所以在不涉及线程安全的场景,比如方法内部,尽量使用 StringBuilder,避免同步带来的消耗.

 @Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

 @Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    @Override
    @HotSpotIntrinsicCandidate
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

四.null

String 是一个 引用型对象,因此也就存在着 null 值,又因 String 是一个经常使用的对象,如果为 null 的话很可能导致整个系统崩溃,因此 String 对 null 值有很高的容错率

public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

对于字符串的拼接也是如此

String s = null;
s = s + "!";
System.out.print(s);// 输出 null!

前面说过对于 “+=” 都是转为 StringBuffer 的 append ,下面就看 AbstractStringBuilder 是如何处理 null 的 (AbstractStringBuilder 是 StringBuffer 和 StringBuilder 的父类 )

 private AbstractStringBuilder appendNull() {
        ensureCapacityInternal(count + 4);
        int count = this.count;
        byte[] val = this.value;
        if (isLatin1()) {
            val[count++] = 'n';
            val[count++] = 'u';
            val[count++] = 'l';
            val[count++] = 'l';
        } else {
            count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
        }
        this.count = count;
        return this;
    }

可以看到也是直接添加 “null” 字符串。

五.保密性

为什么针对安全保密高的信息,char[]比String更好?

因为 String 是不可变的,就是说它一旦创建,就不能更改了,直到垃圾收集器将它回收走。而字符数组中的元素是可以更改的(这就意味着可以在使用完之后将其更改,而不会保留原始的数据)。所以使用字符数组的话,安全保密性高的信息(如密码之类的)将不会存在于系统中被他人看到。

上一篇下一篇

猜你喜欢

热点阅读