Java中的String

1 String是不可变的

String对象一旦被创建就固定不变了,对它的任何改变都不会影响到原对象,相关的操作都会生成新的对象。 以下是String的源码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    
    private final char value[];

    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
}

由上可知Stringfinal类型的,其中的成员变量大部分也是final类型的。

JavaC++不同的点是,在Java中不可能直接操作对象本身,必须通过引用才能访问对象, 包括获取成员变量的值,改变成员变量的值,调用对象的方法等。

String s = "abc";
System.out.println("s = " + s); // s = abc

s = "123";
System.out.println("s = " + s); // s = 123

这里的s只是String类型的引用,而不是对象本身。s = "123";之后又创建了一个新的对象"123",而s又指向了这个新的对象,而原来的"abc"这个对象并没有改变。

在这里插入图片描述

无论是sub操作、concat操作还是replace操作都不是在原有的字符串上进行的,而是重新生成一个新的字符串对象,也就是说,进行这些操作之后,最原始的字符串并没有发生改变。 以下是源码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
  
  private final char value[];

  public String substring(int beginIndex) {
    if (beginIndex < 0) {
      throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
      throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); // 1
  }

  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); // 2
  }

  public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
      int len = value.length;
      int i = -1;
      char[] val = value; /* avoid getfield opcode */

      while (++i < len) {
        if (val[i] == oldChar) {
          break;
        }
      }
      if (i < len) {
        char buf[] = new char[len];
        for (int j = 0; j < i; j++) {
          buf[j] = val[j];
        }
        while (i < len) {
          char c = val[i];
          buf[i] = (c == oldChar) ? newChar : c;
          i++;
        }
        return new String(buf, true); // 3
      }
    }
    return this;
  }
}

String为什么要设计成不可变的?

  • 字符串常量池的需要:如果创建一个String对象时,相关字符串已经存在于常量池中,就不会创建一个新的字符串,而是引用已经存在的字符串。如果允许改变,将导致各种逻辑错误,比如改变一个对象将会影响另一个独立对象,严格来说,这种常量池的思想是一种优化手段;
  • 允许String对象缓存HashCodeJavaString对象的哈希码会被频繁的使用,比如在HashMap中。字符串不变保证了hash码的唯一性,因此可以放心的进行缓存。这也是一种优化手段,意味着不必每次都计算新的哈希码。在String类中有private int hash来缓存hashcode
  • 安全性:String被许多的类来当做参数,如网络url,文件路径path等等,如果String不是固定的,将会引起各种安全隐患;

2 字符串的创建

Java语言中,有8种基本类型和一种比较特殊的类型String。为了使它们在运行过程中速度更快,更节省内存,在Java虚拟机的方法区中提供了一种常量池的概念,常量池可以理解成Java虚拟机提供的缓存。

常量池实际上分为两种状态:静态常量池和运行时常量池。

  • 静态常量池:即.class文件中的常量池,.class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法等信息
  • 运行时常量池:JVM在完成类装载操作后,将.class文件中的常量池载入内存中,并保存在方法区中,我们常说的常量池时指方法区中的运行时常量池。

字符串的创建方式:

  • 直接使用双引号声明的String对象会直接存储在字符串常量池中。 这种方式不会产生垃圾空间
  • 采用new关键字创建的String对象,首先会从字符串常量池中查询当前字符串是否存储,如果存在,直接在堆区创建对象;如果不存在会将当前字符串放入常量池中,并在堆出创建相应的String对象
  • 使用+连接,生成新的字符串。
    • 如果是只有字面量的字符串连接,生成的字符串放在字符串常量池中
    • 如果是包含字符串引用的拼接,首先会在常量池中检查是否有字符串,如果没有,在常量池中创建,并在堆中创建对象,如果有,直接在堆中创建对象

3 String的堆栈问题

public static void main(String[] args) {
  String s1 = "abc";
  String s2 = "abc";
  System.out.println(s1 == s2); // true
}

采用字面量的方式创建一个字符串时,JVM首先会去常量池中查找是否存在abc这个字符串对象,如果不存在,则在字符串常量池中创建"abc"这个对象,然后将abc这个对象的引用地址返回给"abc"对象的引用s1,这样s1就会指向常量池中"abc"这个字符串对象;如果存在,则不创建任何对象,直接将"abc"的地址返回给s1 由此可知,s1s2指向的同一个对象。

public static void main(String[] args) {
  String s3 = new String("xyz");
  String s4 = new String("xyz");
  System.out.println(s3 == s4); // false
  System.out.println(s3.equals(s4)); // true
}

采用new关键字新建一个字符串对象的时候,JVM首先会在常量池中查找有没有xyz这个字符串对象,如果没有,则首先在常量池中创建一个xyz字符串对象,然后再在堆中创建一个xyz字符串对象,将堆中的xyz字符串对象的地址返回给s3;如果有,则不在池中创建xyz这个对象了,直接在堆中创建xyz字符串对象,然后将堆中的xyz这个对象的地址返回给s3 由此可知,s3s4指向不同的对象。

在这里插入图片描述

public static void main(String[] args) {
  String s1 = "hello";
  String s2 = "world";
  String s3 = "helloworld";
  String s4 = "hello" + "world";
  String s5 = "hello" + "world";
  System.out.println("================");
  System.out.println(s3 == s4); // true
  System.out.println(s4 == s5); // true

  String s6 = new String("helloworld");
  String s7 = new String("helloworld");
  String s8 = s7;
  System.out.println("================");
  System.out.println(s3 == s6); // false
  System.out.println(s6 == s7); // false
  System.out.println(s8 == s7); // true

  String s9 = "hello" + new String("world");
  System.out.println("================");
  System.out.println(s9 == s3); // false
  System.out.println(s9 == s6); // false

  String s10 = s1 + s2;
  System.out.println("================");
  System.out.println(s10 == s3); // false
  System.out.println(s10 == s6); // false
  System.out.println(s10 == s9); // false
  
  final String s = "a"; // 用final修饰,相当于一个常量
  String str5 = s + "b";
  System.out.println(str5 == "ab"); // true
}

s4是字面量连接,放在常量池中,所以s3s4指向常量池中的同一个对象;s5s6都是字符串引用拼接,创建的对象都在堆中。

在这里插入图片描述

问:在String str = new String("abc");创建多少个对象?

  1. 在常量池中查找是否有abc,如果没有,在常量池中创建对象"abc"
  2. 在堆中创建一个String类型,值为"abc"的对象,
  3. 将对象地址赋值给str,创建一个引用

因此,如果常量池中没有"abc",则创建两个对象,如果有则创建一个。

问:String str = new String("A" + "B");会创建多少个对象?

字符串常量池:"A""B""AB";堆:new String("AB")str引用

问:String str = new String("ABC") + "ABC"会创建多少个对象?

字符串常量池:ABC;堆:new String("ABC")str引用

问:String a = "abc"; String b = new String("abc");ab是否都能放入HashSet中?

因为a.equals(b)true,所以HashSet中只能放一个。

问:String str1 = "a" + "b" + "c"; String str2 = "abc";会创建几个对象?

字符串常量池:"a""b""c""abc"

4 ==String.equals

两种比较String的方式:==比较的是两个对象的地址,equals比较的是两个对象的内容。

以下是String.equals的源码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
  
  public boolean equals(Object anObject) {
    // 1. 
    if (this == anObject) {
      return true;
    }
    // 2.
    if (anObject instanceof String) {
      String anotherString = (String)anObject;
      int n = length();
      if (n == anotherString.length()) {
        int i = 0;
        while (n-- != 0) {
          if (charAt(i) != anotherString.charAt(i))
            return false;
          i++;
        }
        return true;
      }
    }
    return false;
  }
  
}

由此可知,String.equals方法判断的是值是否相等。

5 StringStringBufferStringBuilder的区别

  • 可变与不可变:String是不可变的字符串对象,StringBuilderStringBuffer是可变字符串对象(其内部的字符串数组长度可变)
  • 是否多线程安全:String中的对象是不可变的,也就可以理解为常量,线程安全。StringBuffer中的方法大多采用了synchronized修饰,是线程安全的,而StringBuilder没有这个修饰,可以认为是非线程安全的。(StringBufferStringBuilder中的方法和功能是等价的)
  • 三者的执行效率:StringBuilder>StringBuffer>String(但这也是相对的,不一定所有的情况都是这样),在字符串相加操作或者改动较少的情况下,建议使用String str = "hello"这种形式;当字符串相加操作较多的情况下,建议使用StringBuilder,多线程的情况下,采用StringBuffer
  • 对象是否可变:String是不可变的对象,每次对String类型进行改变的等同于生成了一个新的String对象,经常改变内容的字符串不建议使用String;对StringBuffer类改变,每次结果都会对StringBuffer对象本身进行操作,而不是生成新的对象,再改变对象引用,经常改变内容的字符串建议使用StringBuffer

参考

https://blog.csdn.net/weixin_40304387/article/details/81071816
https://segmentfault.com/a/1190000009888357
https://www.cnblogs.com/rgever/p/8902210.html
https://zhuanlan.zhihu.com/p/81869136
https://www.cnblogs.com/kuillldan/p/5570675.html
https://www.cnblogs.com/aspirant/p/9193112.html
https://www.cnblogs.com/Alan-Jones/p/9089816.html
https://www.cnblogs.com/xiaoxi/p/6036701.html
https://www.cnblogs.com/Alan-Jones/p/9089816.html
https://blog.csdn.net/werewolf2017/article/details/90043632
https://blog.csdn.net/claram/article/details/53770830
https://blog.csdn.net/zhangjg_blog/article/details/18319521

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值