String

String  被声明final 

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String

          implements java.io.Serializable, Comparable<String>, CharSequence {

                 /** The value is used for character storage. */

                       private final char value[]; }

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

public final class String

                  implements java.io.Serializable, Comparable<String>, CharSequence {

/** The value is used for character storage. */

             private final byte[] value;   //final 修饰   value 数组初始化之后就不能再引用其它数组。

/** The identifier of the encoding used to encode the bytes in {@code value}. */

              private final byte coder;}

 

1、不可变性

        不可变对象是在完全创建后其内部状态保持不变的对象。一旦对象被赋值给变量,我们既不能更新引用,也不能通过任何方式改变内部状态。

String s = "abcd";
s = s.concat("ef");  //在堆中重新创建了一个"abcdef"字符串

        一个string对象在内存(堆)中被创建出来,他就无法被修改。而且,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。

缓存

JVM中专门开辟了一部分空间来存储Java字符串,那就是字符串池

            两个内容相同的字符串变量,可以从池中指向同一个字符串对象,从而节省了关键的内存资源。

String s = "abcd";
String s2 = s;             指向字符串常量池中同一个字符串对象

 用处:可以缓存hashcode    hash值只需要进行一次计算

                      

由于字符串对象被广泛地用作数据结构,它们也被广泛地用于哈希实现,如HashMap、HashTable、HashSet等。在对这些散列实现进行操作时,经常调用hashCode()方法。

不可变性保证了字符串的值不会改变。因此,hashCode()方法在String类中被重写,以方便缓存,这样在第一次hashCode()调用期间计算和缓存散列,并从那时起返回相同的值

            安全性

            线程安全 

                不可变对象可以在同时运行的多个线程之间共享。它们也是线程安全的,因为如 果线程更改了值,那么将在字符串池中创建一个新的字符串,而不是修改相同的值。

            提高性能 

                 字符串不可变,所以可以用字符串池缓存,可以大大节省堆内存。而且还可以提前对hashcode进行缓存,更加高效

  

2、substring()

jdk6 和 jdk7的区别(内存泄漏问题的解决)

jdk6

当调用substring方法的时候,会创建一个新的string对象,但是这个string的值仍然指向堆中的同一个字符数组。只是头尾指针的指向位置不一样

//JDK 6
String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}

public String substring(int beginIndex, int endIndex) {
    //check boundary
    return  new String(offset + beginIndex, endIndex - beginIndex, value);
}

 存在性能问题 :字符数组一直在被引用,所以无法被回收  可能会造成内存泄漏

解决:x = x.substring(x, y) + ""   生成一个新的字符串

 jdk7

substring方法会在堆内存中创建一个新的数组。

//JDK 7
public String(char value[], int offset, int count) {
    //check boundary
    this.value = Arrays.copyOfRange(value, offset, offset + count);
}

public String substring(int beginIndex, int endIndex) {
    //check boundary
    int subLen = endIndex - beginIndex;
    return new String(value, beginIndex, subLen);
}

 其使用new String创建了一个新字符串,避免对老字符串的引用。从而解决了内存泄露问题。

3、字符串常量池

为了减少相同的字符串的重复创建,为了达到节省内存的目的。会单独开辟一块内存,用于保存字符串常量,这个内存区域被叫做字符串常量池。

字符串池化机制:当代码中出现双引号形式(字面量)创建字符串对象时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。

String str = "heyuliang";   //字面量   字符串常量池中没有就创建新对象 有就直接引用

String str = new String("heyuliang");

在JDK 7以前的版本中,字符串常量池是放在永久代中的。

在JDK 7中,将字符串常量池先从永久代中移出,暂时放到了堆内存中。

在JDK 8中,彻底移除了永久代,使用元空间替代了永久代,于是字符串常量池再次从堆内存移动到永久代中

intern:在运行期将字符串内容放置到字符串常量池的办法

             在每次赋值的时候使用 String 的 intern 方法,如果常量池中有相同值,就会重复使用该对象,返回对象引用。

4、 字符串拼接的几种方式

常用的字符串拼接方式有六种,分别是使用+、使用concat、使用StringBuilder、使用StringBuffer以及使用StringUtils.join、StringJoiner(jdk8 )

阿里巴巴Java开发手册建议:循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。

语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

    +  :是将String转成了StringBuilder后,使用其append方法进行处理的。

String wechat = "heyuliang";
String introduce = "长安故里";
String hyL= wechat + "," + introduce;   //(new StringBuilder()).append(wechat).append(",").append(introduce).toString();

    concat:   首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。

   源码:

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);
}
String wechat = "heyuliang";
String introduce = "长安故里";
String hyL= wechat.concat( ",").concat(introduce);

    StringBuilder            char[] value;字符数组 

源码:线程不安全

public StringBuilder append(String str) {
    super.append(str);
    return this;
}
StringBuilder wechat = new StringBuilder("heyuliang");
String introduce = "长安故里";
StringBuilder heyuliang= wechat.append(",").append(introduce);

    StringBuffer

 源码: 线程安全的

public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
StringBuffer wechat = new StringBuffer("heyuliang");
String introduce = "长安故里";
StringBuffer heyuliang= wechat.append(",").append(introduce);

 

拼接字符串效率(高到低):StringBuilder   StringBuffer   concat   StringUtils.join

          StringBufferStringBuilder的基础上,做了同步处理

          StringUtils.join也是使用了StringBuilder,并且其中还是有很多其他操作(适合字符数组和列表拼接)

      如果不是在循环体中进行字符串拼接的话,直接使用+就好了。

      如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder

5、switch (只支持整型int 其它类型也 是通过转化为整型进行操作的 String->hashcode hashcode是int类型)

6、String的长度限制

编译期:String=“” 字面量

字面量要遵守 字符串常量池的规范 不能大于等于 65535

运行期:不能超过Int的范围 Integer.MAX_VALUE

posted @ 2022-03-22 11:07  与长安故里  阅读(213)  评论(0)    收藏  举报