浅谈Java String数据类型

目录

1.String数据类型简介

2.String常量池

3.String为什么设计成不可变的

4.StringBuffer

5.StringBuilder

6.StringUtils


1.String数据类型简介

String表示字符串类型,属于引用数据类型,不属于基本数据类型。

在java中随便使用 双引号括起来 的都是String对象。例如:“a”,“ab”,“hello world!”,这是3个String对象。

我们查看源码可得知,String数据类型是由一个一个char类型拼接起来的:

所以,String类型实际上是一个字符数组,例如字符串"ab c"实际上是char数组['a','b',' ','c']。

所以在java底层,String类型实际上是有通过编码表(ASCII、UTF-8、GBK等),将对应的字符数组转换成对应的数值,再转换成二进制数进行存储。

String被当作变量时,其长度由String存储方式决定。在JAVA SE 9之前,String内部是由char数组存储的,数组最大长度为Integer.MAX_VALUE,即2^31-1,并且char的取值范围在0~65535之间,占两个字节,因此String的最大长度为429496967294字节,运行时需要大约4GB的内存才能存储;JAVA SE 9及其后续版本将char数组改为byte数组,因此String的最大长度为2147483647字节。

2.String常量池

String数据类型不同于其他基本数据类型,它属于引用数据类型,String是一个类,所以他有以下两种声明方式:

String s1 = "abc";
String s2 = new String("abc");

String数据类型的赋值会使用到字符集常量池这一概念。字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。

 

 如上图,给s1直接赋值"abc"时,会先去常量池检索是否有这个值。没有则在常量池中创建该值并存储,然后使得s1指向该地址;有则使得变量直接指向该地址即可。

如使用new String()创建s2赋值"abc",则先会在堆中创建String实例对象,然后指向栈中的常量池中的字符串值。

所以,变量s1的地址是常量池中字符串"abc"的地址,而变量s2的地址是堆中String实例对象的地址。两者字符串值相等,但地址是不一样的。

我们查看以下代码进行验证:

String s1 = "abc";
String s3 = "abc";

System.out.println(s1==s3);

s1==s3比较的是两者的地址,两者都指向常量池中的"abc"的地址,所以会输出为true。

我们进行以下变换:

String s1 = "abc";
String s2 = "ab";
String s3 = s2+"c";
        
System.out.println(s1==s3);

输出结果为false。因为s3为一个变量,不是从常量池中直接获取的"abc"。而String是不可变的,而String的+操作其实是不断新建一个String对象进行拼接。

上述代码实际上是获取变量s2的值后,调用stringBuilder.append()方法给s2赋值。所以s3的"abc"不是从常量池中获得的,而是通过StringBuilder新建的一个对象。

只有当我们的代码为:String s3 = "ab"+"c"时,才是从常量池进行拿值,才会为true。

所以,如果我们要判断两个字符串的值是否相等,会使用equal()方法:

s1.equals(s3)

3.String为什么设计成不可变的

在java中String类型的值是不可改变的。比如我们有一个字符串变量s = “a”,然后我们再对s赋值为”b”,看上去我们改变了字符串s的值,而实际上,是在常量池中新建了一个字符串”b”,然后让s从指向”a”变成了指向”b”。

在String类源码中我们可查得, String类的值value是一个final修饰的变量,也就是不可改变的,String类中也没有相关的构造方法对其值进行修改。而且String类本身也是被final修饰的,所以也不可被继承后再进行修改。

而之所以这样设计,好处有以下:

  1. 如图,当s1和s2同时指向a时,如果修改s1的值为b,使得常量池中a变为b,那么会使得s2的值也随之修改。而让s1指向b进行修改,则不会有以上错误。
  2.  HashMap中String的值是不可变的,才会让查询变得有迹可循(根据Key查Value),不会使得HashCode发送变化。
  3. 线程安全。因为String是不可变的,所以可以被多个线程所共享,不存在线程安全问题。

4.StringBuffer

String是一个不可变的字符序列,而StringBuffer则是一个可变的字符序列。我们可以将其看做一个高级的String。换言之,在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer。

声明方式如下:

        // 定义一个空的字符串缓冲区,含有16个字符的容量
        StringBuffer s1 = new StringBuffer();
        // 定义一个含有10个字符容量的字符串缓冲区
        StringBuffer s2 = new StringBuffer(10);
        // 定义一个含有(16+5)的字符串缓冲区,"ab cd"为5个字符
        StringBuffer s3 = new StringBuffer("ab cd");

下面是StringBuffer的一些常用方法(添加、按下标添加、删除):

        StringBuffer buffer = new StringBuffer("hello");    // 创建一个 StringBuffer 对象
        //追加
        buffer.append(" World!");    
        System.out.println(buffer);
        //根据坐标插入
        buffer.insert(5,",");
        System.out.println(buffer);
        //根据坐标删除
        buffer.delete(6,7);
        System.out.println(buffer);
        //反转字符串内容
        buffer.reverse();
        System.out.println(buffer);

结果如下:

  

 可以看到,append是直接在后面添加字符串;insert是根据坐标(从0开始)插入字符;delete是根据起始和结束坐标,删除字符或字符串;reverse对字符串内容进行反转。

StringBuffer可变的原理,主要是通过数组复制得到的。例如:s1是"abc"要在后面追加"de",他会首先计算好最终的数组长度为5,然后创建一个长度为5的空char数组。再通System.arraycopy()方法把原值复制进去,再把追加值复制在后面。

5.StringBuilder

StringBuilder 与StringBuffer类似,他也是可变的。它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。但由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下会使用 StringBuilder 类。

其内部方法和StringBuffer是一致的。

下面简单介绍一下StringBuffer和StringBuilder和String类型之间的转换:

        StringBuilder builder = new StringBuilder("abc");
        String str = builder.toString();

6.StringUtils

 最后是StringUtils,一般我们会在springboot中通过导入依赖的方式使用:

    <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>3.4</version>
    </dependency>

StringUtils是JDK提供的String类型操作方法的补充,比String操作字符串更加安全,主要解决了尽管输入参数String为null,也不会抛出NullPointException。

下面介绍一些常用的方法:

        String str1 = " ab cd  ";
        String str2 = "  ";
        String str3 = "  ";
        String str4 = "abc d";
        //去除首尾空格
        System.out.println(StringUtils.trim(str1));
        //判空,空格为非空
        System.out.println(StringUtils.isEmpty(str2));
        //判空,空格也为空
        System.out.println(StringUtils.isBlank(str3));
        //首字母大小写转换
        System.out.println(StringUtils.capitalize(str4));
        //替换字符,若被替换的是null,贼返回原字符
        System.out.println(StringUtils.replace(str4," ",","));

输出结果如下:

猜你喜欢

转载自blog.csdn.net/tang_seven/article/details/128893085