一、String、StringBuffer 、StringBuilder
1、定义
用来连接多个字符的,本质就是一个char型的数组,是一种引用类型,并且不能被继承因为是final修饰的
String str = "abc"; 相当于(string底层靠数组实现) char[] data = {'a','b','c'}; String str1 = new String(data);
Java9改进了字符串(包括String、StringBuffer、StringBuilder)的实现。在Java9以前字符串采用char[]数组来保存字符,因此字符串的每个字符占2字节;而Java9的字符串采用byte[]数组再加一个encoding-flag字段来保存字符,因此字符串的每个字符只占1字节。所以Java9的字符串更加节省空间,字符串的功能方法也没有受到影响。
2、创建
//第一种直接创建(先去常量池看有没有这个字符串,有直接使用,没有就会创建)String string = "helloword";
//第二种,至少创建一个对象 如果常量池中有"helloword" 直接让string2指向它 。没有就创建两个String string2 = new String("helloword");
注意:
String string = "";//创建了一个空的字符串,已经初始化了,并分配了内存
String string2 = null;//字符串为空,没有初始化,
3、分类
不可变长的字符串String类(修改之后是创建新的地址)和可变字符串StringBuffer StringBuilder(线程安全的)(修改之后不会创建新的地址)
4、不可变性
string
是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
这个是String类的解释,之前看到这个情况,不能理解上述的解释,如下
看到这里,不明白了,这不是明明已经对他进行修改了吗?为什么还说他是一个不可变类呢?
经过学习,明白String类不可变在哪里体现出来的,接下来就看一张上述a对象的内存存储空间图

可以看出来,再次给a赋值时,并不是对原来堆中实例对象进行重新赋值,而是生成一个新的实例对象,并且指向“456”这个字符串,a则指向最新生成的实例对象,之前的实例对象仍然存在,如果没有被再次引用,则会被垃圾回收。
StringBuffer
StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。

可以看出来,再次给a赋值时,并不是对原来堆中实例对象进行重新赋值,而是生成一个新的实例对象,并且指向“456”这个字符串,a则指向最新生成的实例对象,之前的实例对象仍然存在,如果没有被再次引用,则会被垃圾回收。
StringBuilder
为了能高效拼接字符串,Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象,StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。StringBuffer类中的方法都添加了synchronized关键字,也就是给这个方法添加了一个锁,用来保证线程安全。
StringBuilder还可以进行链式操作
如果我们查看StringBuilder的源码,可以发现,进行链式操作的关键是,定义的append()方法会返回this,这样,就可以不断调用自身的其他方法
注意
对于普通的字符串+操作,并不需要我们将其改写为StringBuilder,因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory的操作。在运行期,StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。
StringBuffer是Java早期的一个StringBuilder的线程安全版本,它通过同步来保证多个线程操作StringBuffer也是安全的,但是同步会带来执行速度的下降。
StringBuilder和StringBuffer接口完全相同,现在完全没有必要使用StringBuffer
5、常用方法
5.1、比较
当我们想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()方法而不能用==
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
从表面上看,两个字符串用==和equals()比较都为true,但实际上那只是Java编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池,自然s1和s2的引用就是相同的。
所以,这种==比较返回true纯属巧合。换一种写法,==比较就会失败:
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
结论:两个字符串比较,必须总是使用equals()方法。
要忽略大小写比较,使用equalsIgnoreCase()方法。
5.2、获取字符串的长度
注意是length()不是length和数字不一样System.out.println("helooeord".length());
5.3、字符串和其他类型转化
基本类型转字符串
Integer a = 1; //第一种 String str = a.toString(); //第二种 String str1 = a+""; //第三种(推荐) String str2 =String.valueOf(a);
字符串转基本类型
Integer hello = Integer.parseInt("123"); Integer str = Integer.valueOf("456");
字符串和字节数组转换
在Java中,char类型实际上就是两个字节的Unicode编码。如果我们要手动把字符串转换成其他编码,可以这样做:
注意:转换编码后,就不再是char类型,而是byte类型表示的数组。
如果要把已知编码的byte[]转换为String,可以这样做:
始终牢记:Java的String和char在内存中总是以Unicode编码表示。
字符串和字符数组转换
String str = "hello"; //字符串转字符数组 char[] c = str.toCharArray(); //字符数组转字符串 String str1 = new String(c);
5.4、格式化
String.format()字符串常规类型格式化的两种重载方式
- format(String format, Object… args) 新字符串使用本地语言环境,制定字符串格式和参数生成格式化的新字符串。
- format(Locale locale, String format, Object… args) 使用指定的语言环境,制定字符串格式和参数生成格式化的字符串。
上个栗子有用到了字符类型和整数类型的格式化 下面我把常用的类型例举出来
转换符 | 详细说明 | 示例 |
%s | 字符串类型 | “喜欢请收藏” |
%c | 字符类型 | ‘m’ |
%b | 布尔类型 | true |
%d | 整数类型(十进制) | 88 |
%x | 整数类型(十六进制) | FF |
%o | 整数类型(八进制) | 77 |
%f | 浮点类型 | 8.888 |
%a | 十六进制浮点类型 | FF.35AE |
%e | 指数类型 | 9.38e+5 |
%g | 通用浮点类型(f和e类型中较短的) | 不举例(基本用不到) |
%h | 散列码 | 不举例(基本用不到) |
%% | 百分比类型 | %(%特殊字符%%才能显示%) |
%n | 换行符 | 不举例(基本用不到) |
%tx | 日期与时间类型(x代表不同的日期与时间转换符) | 不举例(基本用不到) |
使用
搭配转换符还有实现高级功能 第一个例子中有用到 $
标志 | 说明 | 示例 | 结果 |
+ | 为正数或者负数添加符号 | (“%+d”,15) | +15 |
0 | 数字前面补0(加密常用) | (“%04d”, 99) | 0099 |
空格 | 在整数之前添加指定数量的空格 | (“% 4d”, 99) | 99 |
, | 以“,”对数字分组(常用显示金额) | (“%,f”, 9999.99) | 9,999.990000 |
( | 使用括号包含负数 | (“%(f”, -99.99) | (99.990000) |
# | 如果是浮点数则包含小数点,如果是16进制或8进制则添加0x或0 | (“%#x”, 99)(“%#o”, 99) | 0x63 0143 |
< | 格式化前一个转换符所描述的参数 | (“%f和%<3.2f”, 99.45) | 99.450000和99.45 |
d,%2$s”, 99,”abc”) | 99,abc |
第一个例子中有说到 %tx x代表日期转换符 我也顺便列举下日期转换符
标志 | 说明 | 示例 |
c | 包括全部日期和时间信息 | 星期六 十月 27 14:21:20 CST 2007 |
F | “年-月-日”格式 | 2007-10-27 |
D | “月/日/年”格式 | 10/27/07 |
r | “HH:MM:SS PM”格式(12时制) | 02:25:51 下午 |
T | “HH:MM:SS”格式(24时制) | 14:28:16 |
R | “HH:MM”格式(24时制) | 14:28 |
使用
5.5、搜索子串、提取子串。常用的方法有
注意到contains()方法的参数是CharSequence而不是String,因为CharSequence是String的父类。
搜索子串的更多的例子:
提取子串的例子:
注意索引号是从0开始的。
5.6、去除首尾空白字符
使用trim()方法可以移除字符串首尾空白字符。空白字符包括空格,\t,\r,\n:
注意:trim()并没有改变字符串的内容,而是返回了一个新字符串。
另一个strip()方法也可以移除字符串首尾空白字符。它和trim()不同的是,类似中文的空格字符\u3000也会被移除:
String还提供了isEmpty()和isBlank()来判断字符串是否为空和空白字符串:
5.7、替换子串
要在字符串中替换子串,有两种方法。一种是根据字符或字符串替换:
另一种是通过正则表达式替换:
上面的代码通过正则表达式,把匹配的子串统一替换为","。关于正则表达式的用法我们会在后面详细讲解
5.8、分割字符串
要分割字符串,使用split()方法,并且传入的也是正则表达式:
5.9、拼接字符串
拼接字符串使用静态方法join(),它用指定的字符串连接字符串数组:
二、StringJoiner
要高效拼接字符串,应该使用StringBuilder。
很多时候,我们拼接的字符串像这样:
类似用分隔符拼接数组的需求很常见,所以Java标准库还提供了一个StringJoiner来干这个事:
用StringJoiner的结果少了前面的"Hello "和结尾的"!"!遇到这种情况,需要给StringJoiner指定“开头”和“结尾”:
String.join()
String还提供了一个静态方法join(),这个方法在内部使用了StringJoiner来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()更方便:
















