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字节。
String数据类型不同于其他基本数据类型,它属于引用数据类型,String是一个类,所以他有以下两种声明方式:
String s1 = "abc";
String s2 = new String("abc");
String数据类型的赋值会使用到字符集常量池这一概念。字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
如使用new String()创建s2赋值"abc",则先会在堆中创建String实例对象,然后指向栈中的常量池中的字符串值。
我们查看以下代码进行验证:
String s1 = "abc";
String s3 = "abc";
System.out.println(s1==s3);
我们进行以下变换:
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)
在java中String类型的值是不可改变的。比如我们有一个字符串变量s = “a”,然后我们再对s赋值为”b”,看上去我们改变了字符串s的值,而实际上,是在常量池中新建了一个字符串”b”,然后让s从指向”a”变成了指向”b”。
在String类源码中我们可查得, String类的值value是一个final修饰的变量,也就是不可改变的,String类中也没有相关的构造方法对其值进行修改。而且String类本身也是被final修饰的,所以也不可被继承后再进行修改。
而之所以这样设计,好处有以下:
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()方法把原值复制进去,再把追加值复制在后面。
StringBuilder 与StringBuffer类似,他也是可变的。它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。但由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下会使用 StringBuilder 类。
其内部方法和StringBuffer是一致的。
下面简单介绍一下StringBuffer和StringBuilder和String类型之间的转换:
StringBuilder builder = new StringBuilder("abc");
String str = builder.toString();
最后是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," ",","));
输出结果如下: