String学习笔记

阿里Java开发手册

这里总结了阿里开发手册中有关String类的介绍

13. 【推荐】使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。

说明:

String str = "a,b,c,,";

String[] ary = str.split(",");

// 预期大于3,结果是3

System.out.println(ary.length);

17. 【推荐】循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。

说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。

反例:

String str = "start";
for (int i = 0; i < 100; i++) {
    str = str + "hello";
}

String

概述

首先我们要明确,String 并不是基本数据类型,而是一个对象,并且是不可变的对象。查看源码就会发现 String 类为 final 型的(当然也不可被继承),而且通过查看 JDK 文档会发现几乎每一个修改 String 对象的操作,实际上都是创建了一个全新的 String 对象。

字符串为对象,那么在初始化之前,它的值为 null,到这里就有必要提下 ””、null、new String() 三者的区别。null 表示 string 还没有 new ,也就是说对象的引用还没有创建,也没有分配内存空间给他,而””、new String() 则说明了已经 new 了,只不过内部为空,但是它创建了对象的引用,是需要分配内存空间的。

String源码分析

从这段代码看出继承关系

我们得出的几个重点是:

  1. String类的底层使用 char 的数组保存数据。
  2. String类是一个 final 类,不允许被继承。
  3. 它实现了三个接口,分别为 Serializable、Comparable 和 CharSequence。
  4. String类是一个 immutable 类,该类的对象生成后,内容不会发生变化。该类中的所有返回String类型对象的成员方法都是返回一个新的String对象

final不可继承与不可变

写过 Java 的人都知道, 当 final 关键字修饰类时,代表此类不可继承。所以 String 类是不能被外部继承。这时候我们可能会好奇,String 的设计者 为什么要把它设计成不可继承的呢。我在知乎上找到了相关的问题和讨论, 我觉得首位的回答已经说的很明白了。String 做为 Java 的最基础的引用数据类型,最重要的一点就是不可变性,所以使用 final 就是为了禁止继承 破坏了 String 的不可变的性质

实现类的不可变性,不光是用 final 修饰类这么简单,从源码中可以看到,String 实际上是对一个字符数组的封装,而字符数组是私有的,并且没有提供任何可以修改字符数组的方法,所以一旦初始化完成, String 对象便无法被修改。

Serializable序列化

从上面的类定义中我们看到了 String 实现了序列化的接口 Serializable,所以 String 是支持序列化和反序列化的。 什么是Java对象的序列化?相信很多和我一样的 Java 菜鸟都有这样疑问。深入分析Java的序列化与反序列化这篇文章中的这一段话 解释的很好。

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下, 只有当JVM处于运行时,这些对象才可能存在, 即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中, 就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。 Java对象序列化就能够帮助我们实现该功能。 使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。 必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。 除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。 Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

在 String 源码中,我们也可以看到支持序列化的类成员定义。

serialVersionUID 是一个序列化版本号,Java 通过这个 UID 来判定反序列化时的字节流与本地类的一致性,如果相同则认为一致, 可以进行反序列化,如果不同就会抛出异常。

Comparable可排序

String 类还实现了 Comparable 接口,Comparable<T>接口只有一个方法 public int compareTo(T o),实现了这个接口就意味着该类支持排序, 即可用 Collections.sort 或 Arrays.sort 等方法对该类的对象列表或数组进行排序。

在 String 中我们还可以看到这样一个静态变量,

/**
 * A Comparator that orders {@code String} objects as by
 * {@code compareToIgnoreCase}. This comparator is serializable.
 * <p>
 * Note that this Comparator does <em>not</em> take locale into account,
 * and will result in an unsatisfactory ordering for certain locales.
 * The java.text package provides <em>Collators</em> to allow
 * locale-sensitive ordering.
 *
 * @see     java.text.Collator#compare(String, String)
 * @since   1.2
 */
public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                     = new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
        implements Comparator<String>, java.io.Serializable {
    // use serialVersionUID from JDK 1.2.2 for interoperability
    private static final long serialVersionUID = 8575799808933029326L;

    public int compare(String s1, String s2) {
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }

    /** Replaces the de-serialized object. */
    private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}

从上面的源码中可以看出,这个静态成员是一个实现了 Comparator 接口的类的实例,而实现这个类的作用是比较两个忽略大小写的 String 的大小。

那么 Comparable 和 Comparator 有什么区别和联系呢?同时 String 又为什么要两个都实现一遍呢?

第一个问题这里就不展开了,总结一下就是,Comparable 是类的内部实现,一个类能且只能实现一次,而 Comparator 则是外部实现,可以通过不改变 类本身的情况下,为类增加更多的排序功能。 所以我们也可以为 String 实现一个 Comparator使用,具体可以参考Comparable与Comparator的区别这篇文章。

String 实现了两种比较方法的意图,实际上是一目了然的。实现 Comparable 接口为类提供了标准的排序方案,同时为了满足大多数排序需求的忽略大小写排序的情况, String 再提供一个 Comparator 到公共静态类成员中。如果还有其他的需求,那就只能我们自己实现了。

CharSequence

接口中的方法

我们发现这个接口中的方法很少,没有我们常用的String方法,

那么它应该是一个通用接口,会有很多实现类,包括StringBuilder和StringBuffer

String的方法

String 的方法大致可以分为以下几类。

  • 构造方法
  • 功能方法
  • 工厂方法
  • intern方法

关于 String 的方法的解析,这篇文章已经解析的够好了,所以我这里也不再重复的说一遍了。这里主要分析构造方法和intern方法

String构造方法,源码分析

通过源码可以看到String的构造方法很多,下面找几个典型的讲一讲

String()

解析

String str=new String("abc");

  1. 先创建一个空的String对象
  2. 常量池中创建一个abc,并赋值给第二个String
  3. 将第二个String的引用传递给第一个String

注:如果常量池中有abc,则不用创建,直接把引用传递给第一个String

String(String original)

案例:  String str=new String("str"); 

String(char value[])

注:将传过来的char数组copy到value数组里

字节数组初始化

byte类型的方法有8个,两个过时的
剩下六个又分为指定编码和不指定编码

解析

  • byte是网络传输或存储的序列化形式,
  • 所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化,
  • byte是字节,char是字符,字节流和字符流需要指定编码,不然可能会乱码,
  • bytes字节流是使用charset进行编码的,想要将他转换成unicode的char[]数组,而又保证不出现乱码,那就要指定其解码方法

注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。

==和equals

==

对于 ==,如果作用与基本数据类型(byte、short、char、int、long、float、double、boolean)的变量,则比较的是其存储的“值”是否相等;如果作用与引用类型的变量,则比较其所指向的对象的地址是否相同(即是否同一个对象)。在Java中,String是引用类型。

equals

String的 equals 方法继承自Java中的超级父类Object,Object的equals方法用来比较两个对象的引用是否相等(即是否同一个对象)。但是,String的equals方法不仅是简单地继承,而是进行了重写(Override),用来比较两个String对象所存储的字符序列值是否相等。

String类重写了父类equals的方法
我们先看下父类的

//直接判断地址

再看下String类的equals

public boolean equals(Object anObject) {
    //地址相等肯定为true,就不用继续往下走了
    if (this == anObject) {
        return true;
    }
    //地址不相等的情况下,比较两个字符的内容是否一样
    //把字符串方法char[]数组里,遍历比较
    if (anObject instanceof 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 (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

创建对象数量

创建方式

对象个数

引用指向

String a="abc";

1

常量池

String b=new String("abc");

1

堆内存 (abc则是复制的常量池里的abc)

String c=new String()

1

堆内存

String d="a"+"bc";

3

常量池(a一次,bc一次,和一次,d指向和)

String e=a+b;

3

堆内存

重点--两个字符串常量或者字面量相加,不会new新的字符串,
变量相加则是会new新的字符串,new出来的都在堆

要想了解这个,要先了解

字面量和运行时常量池

JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串常量池。

在JVM运行时区域的方法区中,有一块区域是运行时常量池,主要用来存储编译期生成的各种字面量符号引用

new String创建了几个对象

下面,我们可以来分析下String s = new String("CrankZ");创建对象情况了。

这段代码中,我们可以知道的是,在编译期,符号引用s和字面量CrankZ会被加入到Class文件的常量池中,然后在类加载阶段(具体时间段参考Java 中new String("字面量") 中 "字面量" 是何时进入字符串常量池的?),这两个常量会进入常量池。

但是,这个“进入”阶段,并不会直接把所有类中定义的常量全部都加载进来,而是会做个比较,如果需要加到字符串常量池中的字符串已经存在,那么就不需要再把字符串字面量加载进来了。

所以,当我们说<若常量池中已经存在"CrankZ",则直接引用,也就是此时只会创建一个对象>说的就是这个字符串字面量在字符串池中被创建的过程。

说完了编译期的事儿了,该到运行期了,在运行期,new String("CrankZ");执行到的时候,是要在Java堆中创建一个字符串对象的,而这个对象所对应的字符串字面量是保存在字符串常量池中的。但是,String s = new String("CrankZ");,对象的符号引用s是保存在Java虚拟机栈上的,他保存的是堆中刚刚创建出来的的字符串对象的引用。

所以,你也就知道以下代码输出结果为false的原因了。

String s1 = new String("CrankZ");
String s2 = new String("CrankZ");
System.out.println(s1 == s2);    // false

因为,==比较的是s1和s2在堆中创建的对象的地址,当然不同了。但是如果使用equals,那么比较的就是字面量的内容了,那就会得到true。

所以,String s = new String("CrankZ");创建几个对象的答案你也就清楚了。

常量池中的“对象”是在编译期就确定好了的,在类被加载的时候创建的,如果类加载时,该字符串常量在常量池中已经有了,那这一步就省略了。堆中的对象是在运行期才确定的,在代码执行到new的时候创建的。

String 的hashcode()方法

String也是遵守equals的标准的,也就是 s.equals(s1)为true,则s.hashCode()==s1.hashCode()也为true。此处并不关注eqauls方法,而是讲解 hashCode()方法,String.hashCode()有点意思,而且在面试中也可能被问到。先来看一下代码:

为什么要选31作为乘数呢?

从网上的资料来看,一般有如下两个原因:

  • 为减小哈希碰撞,通常选择质数作为乘数,31是质数。
  • 31可以被 JVM 优化,31 * i = (i << 5) - i。

详见:http://www.cnblogs.com/jinggod/p/8425182.html

https://www.cnblogs.com/nullllun/p/8350178.html

https://juejin.im/post/5ba75d165188255c6a043b96

intern方法

java语法设计的时候针对String,提供了两种创建方式和一种特殊的存储机制(String intern pool )。

两种创建字符串对象的方式:

  1. 字面值的方式赋值
  2. new关键字新建一个字符串对象

这两种方法在性能和内存占用方面存在这差异

String Pool串池

是在内存堆中专门划分一块空间,用来保存所有String对象数据,当构造一个新字符串String对象时(通过字面量赋值的方法),Java编译机制会优先在这个池子里查找是否已经存在能满足需要的String对象,如果有的话就直接返回该对象的地址引用(没有的话就正常的构造一个新对象,丢进去存起来),这样下次再使用同一个String的时候,就可以直接从串池中取,不需要再次创建对象,也就避免了很多不必要的空间开销。

注:方法注释会有写到,意思就是调用方法时,

如果常量池有当前String的值,就返回这个值,没有就加进去,返回这个值的引用

intern 方法的作用

Oracle JDK 中,intern 方法被 native 关键字修饰并且没有实现,这意味着这部分到实现是隐藏起来了。从注释中看到,这个方法的作用是如果常量池 中存在当前字符串,就会直接返回当前字符串,如果常量池中没有此字符串,会将此字符串放入常量池中后再返回。通过注释的介绍已经可以明白这个方法的作用了, 再用几个例子证明一下。

public static void main(String[] args) throws Exception {
    String s1 = "hello";
    String s2 = new String("hello");
    String s3 = s2.intern();
    System.out.println(s1 == s2);    // false
    System.out.println(s1 == s3);    // true
}

运行结果

这里就很容易的了解 intern 实际上就是把普通的字符串对象也关联到常量池中。

当然 intern 的实现原理和最佳实践等也是需要理解学习的,美团技术团队的这篇深入解析String#intern 很深入也很详细,推荐阅读。

字符串拼接方式

  1. +
  2. concat()
  3. append()

拼接方法+,append(),concat()

对于字符串而言我们经常是要对其进行拼装处理的,在 Java 中提供了三种拼装的方法:+、concat() 以及 append() 方法。这三者之间存在什么区别呢?先看如下示例:

public static void main(String[] args) {
    //+
    long start_01 = System.currentTimeMillis();
    String a = "a";
    for (int i = 0; i < 100000; i++) {
        a += "b";
    }
    long end_01 = System.currentTimeMillis();
    System.out.println(" + 所消耗的时间:" + (end_01 - start_01) + "毫秒");
    //concat()
    long start_02 = System.currentTimeMillis();
    String c = "c";
    for (int i = 0; i < 100000; i++) {
        c = c.concat("d");
    }
    long end_02 = System.currentTimeMillis();
    System.out.println("concat所消耗的时间:" + (end_02 - start_02) + "毫秒");
    //append
    long start_03 = System.currentTimeMillis();
    StringBuffer e = new StringBuffer("e");
    for (int i = 0; i < 100000; i++) {
        e.append("d");
    }
    long end_03 = System.currentTimeMillis();
    System.out.println("append所消耗的时间:" + (end_03 - start_03) + "毫秒");
}

运行结果

从上面的运行结果可以看出,append()速度最快,concat()次之,+最慢。原因请看下面分解:

1、+

在前面我们知道编译器对+进行了优化,它是使用 StringBuilder 的 append() 方法来进行处理的,我们知道 StringBuilder 的速度比 StringBuffer 的速度更加快,但是为何运行速度还是那样呢?主要是因为编译器使用 append() 方法追加后要同 toString() 转换成 String 字符串,也就说 str +=”b”等同于

str = new StringBuilder(str).append(“b”).toString();

它变慢的关键原因就在于 new StringBuilder() 和 toString(),这里可是创建了 10 W 个 StringBuilder 对象,而且每次还需要将其转换成 String,速度能不慢么?

2、concat()

这是 concat() 的源码,它看上去就是一个数字拷贝形式,我们知道数组的处理速度是非常快的,但是由于该方法最后是这样的:return new String(0, count + otherLen, buf);这同样也创建了 10 W 个字符串对象,这是它变慢的根本原因。

3、append()

StringBuffer 的 append() 方法是直接使用父类 AbstractStringBuilder 的 append() 方法,该方法的源码如下:

与 concat() 方法相似,它也是进行字符数组处理的,加长,然后拷贝,但是请注意它最后是返回并没有返回一个新串,而是返回本身,也就说这这个 10 W 次的循环过程中,它并没有产生新的字符串对象。

通过上面的分析,我们需要在合适的场所选择合适的字符串拼接方式,但是并不一定就要选择 append() 和 concat() 方法,原因在于+根据符合我们的编程习惯,只有到了使用 append() 和 concat() 方法确实是可以对我们系统的效率起到比较大的帮助,才会考虑,同时鄙人也真的没有怎么用过 concat() 方法。

String…

String… excludeProperty表示不定参数,也就是调用这个方法的时候这里可以传入多个String对象。

举例:

public static void main(String[] args) {
    //测试,传入多个参数
    test("hello", "world", "123", "china", "csdn", "com");
}
public static void test(String... arguments) {
    for (int i = 0; i < arguments.length; i++) {
        System.out.println(arguments[i]);
    }
}

String为什么不可变,如何实现的?

被final修饰的类,两层含义

1、不可改变

2、不可继承

实现类的不可变性,不光是用 final 修饰类这么简单,从源码中可以看到,String 实际上是对一个字符数组的封装,而字符数组是私有的,并且没有提供 任何可以修改字符数组的方法,所以一旦初始化完成, String 对象便无法被修改。

通过对属性私有化,final修饰,同时没有提供公开的get set方法以及其他的能够修改属性的方法,保证了在创建之后不会被从外部修改。

同时不能忘了,String也是被final修饰的,在之前的文章中我们提到过,final修饰类的结果是String类没有子类。

那么String真的不能改变吗?

不是,通过反射我们可以,代码如下:

public static void main(String[] args) throws Exception {
    String c = new String("abc");
    System.out.println(c);
    //获取String类中的value字段
    Field valueFieldOfString = String.class.getDeclaredField("value");
    //改变value属性的访问权限
    valueFieldOfString.setAccessible(true);
    //获取s对象上的value属性的值
    char[] value = (char[]) valueFieldOfString.get(c);
    //改变value所引用的数组中的第5个字符
    value[1] = '_';
    System.out.println(c);
}

执行的结果是

也就是说我们改变了字符串对象的值,有什么意义呢?没什么意义,我们从来不会这么做。

String为什么被设计成不可变?

效率/性能

在jdk 1.7之后,方法区的字符串常量池移至堆中

常量池是为了避免频繁的创建和销毁对象而影响系统性能。例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中,常量池的优点就是数据共享,例如下面代码中,我们定义了2个String类型的字符串,那么在字符串常量池中,只会存在1份”java”字面量对象。(new String(“”)情况另外下面会继续分析)

        String a="java";

        String b="java";这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。

不可变性的优点

安全性

字符串不可变安全性的考虑处于两个方面,数据安全和线程安全。

  1. 数据安全
  2. 线程安全

数据安全

数据安全,大家可以回忆一下,我们都在哪些地方大量的使用了字符串?网络数据传输,文件IO等,也就是说当我们在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。

String被广泛的使用在其他Java类中充当参数。比如网络连接、打开文件等操作。如果字符串可变,那么类似操作可能导致安全问题。因为某个方法在调用连接操作的时候,他认为会连接到某台机器,但是实际上并没有(其他引用同一String对象的值修改会导致该连接中的字符串内容被修改)。可变的字符串也可能导致反射的安全问题,因为他的参数也是字符串。

那么为何String要被声明成final类型呢,原因简单的说有两点:

//不可变的String
public static String changeStr(String s) {
    s += "bbb";
    return s;
}

// 可变的StringBuilder
public static StringBuilder changeSb(StringBuilder sb) {
    return sb.append("bbb");
}

public static void main(String[] args) throws Exception {
    //String做参数
    String s = new String("aaa");
    String ns = changeStr(s);
    System.out.println("String aaa >>> " + s.toString());

    // StringBuilder做参数
    StringBuilder sb = new StringBuilder("aaa");
    StringBuilder nsb = changeSb(sb);
    System.out.println("StringBuilder aaa >>> " + sb.toString());
}

运行结果

如果程序员不小心像上面例子里,直接在传进来的参数上加”bbb”,因为Java对象参数传的是引用,所以可变的的StringBuffer参数就被改变了。

可以看到变量sb在changeSb(sb)操作之后,就变成了”aaabbb”。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。

线程安全

线程安全,因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享,这样便不用因为线程安全问题而使用同步。

常见用法

1、创建格式化字符串

创建格式化字符串有两种方法printf()和format(),下面直接举例

如下所示:

System.out.printf("浮点型变量的值为 " + "%f, 整型变量的值为 " + " %d, 字符串变量的值为 " + "is %s", floatVar, intVar, stringVar);

你也可以这样写

String fs;
fs = String.format("浮点型变量的值为 " + "%f, 整型变量的值为 " + " %d, 字符串变量的值为 " + " %s", floatVar, intVar, stringVar);

2.转换String为数字

int age = Integer.parseInt("10");
long id = Long.parseLong("190"); // 假如值可能很大.

3. 如何将String转换为日期?

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
String str = "2013-11-07";
Date date = format.parse(str);
System.out.println(format.format(date));//2013-11-07

4. 如何统计某个字符出现的次数?

同样使用Apache Commons Lang 库 StringUtils  类:

int n = StringUtils.countMatches("11112222", "1");
System.out.println(n);

Java7

1、在switch语句中使用String作为case条件

从 JDK7 开始,这是可以的,啰嗦一句,Java 6 及以前的版本都不支持这样做.

// 只在java 7及更高版本有效!
switch (str.toLowerCase()) {
    case "a":
        value = 1;
        break;
    case "b":
        value = 2;
        break;
}

详看:http://www.kissyu.org/2016/05/23/java%20String%20switch%E7%9A%84%E5%AE%9E%E7%8E%B0/

2、substring()方法

在JDK6中,substring()方法还是共用原来的char[]数组,通过偏移和长度构造了一个"新"的String。
想要substring()取得一个全新创建的对象,使用如下这种方式:
String sub = str.substring(start, end) + "";

当然 Java 7 中,substring()创建了一个新的char[] 数组,而不是共用.

想要了解更多,请参考:  JDK6和JDK7中substring()方法及其差异

3、jdk6 和 jdk7 下 intern 的区别

相信很多 JAVA 程序员都做做类似 String s = new String("abc")这个语句创建了几个对象的题目。 这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是"abc"字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。

来看一段代码:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

打印结果是

  • jdk6 下false false
  • jdk7 下false true

详看:https://tech.meituan.com/in_depth_understanding_string_intern.html

Java9

Java9后String的空间优化

前言

据我所知 Java 开发人员几乎任何时候都会想到 String,字符串确实已经成为最常用的类了,而且是大量使用。我们都知道,String 其实是封装了字符,里面必须由字符或字节数组来存放,从 Java9 开始 Java 语言开发者对 String 做了一些空间的优化。

从char到byte

JDK9 之前的库的 String 类的实现使用了 char 数组来存放字符串,char 占用16位,即两字节。

private final char value[];

这种情况下,如果我们要存储字符A,则为0x00 0x41,此时前面的一个字节空间浪费了。但如果保存中文字符则不存在浪费的情况,也就是说如果保存 ISO-8859-1 编码内的字符则浪费,之外的字符则不会浪费。

而 JDK9 后 String 类的实现使用了 byte 数组存放字符串,每个 byte 占用8位,即1字节。

private final byte[] value

编码

String 支持多种编码,但如果不指定编码的话,它可能使用两种编码,分别为 LATIN1 和 UTF16。LATIN1 可能比较陌生,其实就是 ISO-8859-1 编码,属于单字节编码。而 UTF16 为双字节编码,它使用1个或2个16位长的空间存储。

详看:

https://juejin.im/post/5aff7f10518825426e0233ea

https://blog.csdn.net/wangyangzhizhou/article/details/80371653

Java11

Java 11 增加了一系列的字符串处理方法,如以下所示。

// 判断字符串是否为空白
" ".isBlank();                // true

// 去除首尾空格
" Javastack ".strip();          // "Javastack"

// 去除尾部空格
" Javastack ".stripTrailing();  // " Javastack"

// 去除首部空格
" Javastack ".stripLeading();   // "Javastack "

// 复制字符串
"Java".repeat(3);             // "JavaJavaJava"

// 行数统计
"A\nB\nC".lines().count();    // 3

详看:https://juejin.im/post/5c11034f51882516eb564c72

参考

https://juejin.im/post/5c0fb9bbe51d451dd867a96a

https://juejin.im/post/5b972e3c6fb9a05d171d2506

https://blog.csdn.net/renfufei/article/details/14448147

http://www.hollischuang.com/archives/1230

https://juejin.im/post/59746047518825594f0eff0a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值