目录
StringBuilder & StringBuffer 类
String & StringBuilder & StringBuffer 的区别
认识字符串
Java 中的 String 类代表字符串 , 在程序中使用 " " 定义的内容都是字符串 , 字符串是常量 , 它们的值在创建之后不能更改.
1.创建字符串
String类的构造方法
public String()
// 初始化新创建的 String 对象,使其表示空字符序列。
public String(byte[] bytes)
// 通过使用平台的默认字符集解码指定的字节数组来构造新的 String 。
public String(byte[] bytes,Charset charset)
// 构造一个新的 String 由指定用指定的字节的数组解码 charset
public String(byte[] bytes, int offset, int length)
// 通过使用平台的默认字符集解码指定的字节子阵列来构造新的 `String` 。
public String(byte[] bytes, int offset, int length, Charset charset)
// 构造一个新的 String 通过使用指定的指定字节子阵列解码charset
public String(byte[] bytes, int offset, int length, String charsetName)
// 构造一个新的 String 通过使用指定的字符集解码指定的字节子阵列。
public String(byte[] bytes, String charsetName)
// 构造一个新的 String 由指定用指定的字节的数组解码charset
public String(char[] value)
// 分配一个新的 String ,以便它表示当前包含在字符数组参数中的字符序列。
public String(char[] value, int offset, int count)
// 分配一个新的 String ,其中包含字符数组参数的子阵列中的字符。
public String(int[] codePoints, int offset, int count)
// 分配一个新的 String ,其中包含 Unicode code point数组参数的子阵列中的字符
public String(String original)
// 初始化新创建的 String 对象,使其表示与参数相同的字符序列
public String(StringBuffer buffer)
// 分配一个新的字符串,其中包含当前包含在字符串缓冲区参数中的字符序列。
public String(StringBuilder builder)
// 分配一个新的字符串,其中包含当前包含在字符串构建器参数中的字符序列。
常见的构造方式
String 类提供的构造方法太多太多了 , 挑几个常见或者常用的创建方式给大家演示一下
-
方式一 , 最常用也最简单的方式 , 直接赋值
String str = "Hello";
-
方式二 , 给定字符串创建一个实例
String str = new String("Hello");
-
方式三 , 给定一个字符数组构造字符串
char[] array = {'H','e','l','l','o'}; String str = new String(array);
2.字符串比较相等
双等号==比较字符串
以上每种创建方式达到的效果都是一样 , 唯一的区别就是内存布局不一样了 , 先看一段代码
public static void main(String[] args) {
String str1 = "Hello";
String str2 = new String("Hello");
System.out.println(str1 == str2);
String str3 = "He" + "llo";
System.out.println(str1 == str3);
String str4 = "He";
String str5 = "llo";
String str6 = str4 + str5;
System.out.println(str1 == str6);
}
// 运行结果
false
true
false
为什么明明看着差不多的代码 , 得到的答案就不是一样的呢 ? 这是因为 String 类是个引用类型 , 如果引用类型之间使用 == 比较 , 那么比较的就是引用类型的指向的对象是否相等 , 既然是比较对象为什么又会有相同的呢 ? 那么接下来我们分段来解析以上代码
代码一
String str1 = "Hello";
String str2 = new String("Hello");
System.out.println(str1 == str2);
// 运行结果
false
首先我们要明确一个概念 , " " 引起的内容都是一个常量 , 而这个常量会放在内存中的堆区 ( 在JDK1.7开始,被移到堆区中 ) 的字符串常量池中 , 常量池中的字符串也只能存在一份 , 接下来看一下 代码 1 的内存布局
在代码1中 , str1 变量是直接指向的是在常量池中 "Hello" 的地址 , 而 str2 变量指向的是堆区另外一块地址 , 而那一块地址指向的是常量池中的 "Hello" 的地址 , 这是为什么 ? 这里就要先看看 String 类的底层实现了 , 如果你使用的开发工具是 IDEA 的话 , 只需要按住 ctrl + 鼠标左键 点击 代码中的 String 类 , 就能转到实现文件
点进来可以看到 , 在 String 类底层是用一个 value 字符数组用来存储字符串的 , 还有另一个变量用来存储字符串的 哈希 地址
回到代码中 , 我们查看一下 str2 的构造方法是如果实现的
点进来可以看到 , 构造方法是用本类的 value 数组引用传入的字符串参数的 value 数组 , 本类的 hash 变量保存 传入的字符串参数的 hash 变量 , 况且用new这个关键字的话,是调用new指令创建一个对象,然后调用构造方法来初始化这个对象 , 而 new 出来的对象都是放在堆区的 , 那么此时在内存的堆区就新创建了一个 String 类的对象 , 而这个对象指向的就是 字符串常量池 中的 "Hello" , 而 str2 变量引用的是堆区的 String 对象
现在可以知道 str1 变量是直接指向 字符串常量池 的 "Hello" 的地址 , 而 str2 变量是指向堆区中的 String 对象的地址 , 两个指向的地址不同 , 所以结果是 false
代码二
String str1 = "Hello";
String str3 = "He" + "llo";
System.out.println(str1 == str3);
// 运行结果
true
代码 2 的结果为 true 的原因也很简单 , 因为 "He" 和 "llo" 两个都是字符串常量 , 代码在编译期间就会自动拼接成 "Hello"
所以 , str3 的代码就相当于 "String str3 = "Hello"; , 由于前面 str1 指向的 Hello 已经放入到在 字符串常量池 中了 , 所以 str3 变量产生的 "Hello" 字符串准备放入 字符串常量池 时 , 发现 "Hello" 已经存在 , JVM 此时就会将 字符串常量池 中的 "Hello"地址返回给 str3 ,这时 str1 和 str3 指向的都是同一块地址 , 所以两个变量比较的结果等于 true
代码三
String str1 = "Hello";
String str4 = "He";
String str5 = "llo";
String str6 = str4 + str5;
System.out.println(str1 == str6);
这个代码看过去跟代码2差不多 , 也是两个字符串相加, 为什么这个比较结果是 false 呢 ? 这是因为 str4 和 str5 是变量 , 在编译期间 , 并不知道 变量中放的是什么内容 , 只有在运行的时候才能知道 , 来看一下内存图
为什么 str6 会在堆中产生一个 String 对象呢 ? 我们可以反编译代码查看一下执行过程 , 先在 IDEA 中找到终端 , 然后将目录切换至要反编译的字节码文件目录下 , 输入 Javap -c <字节码文件> >> <指定文件>
指令 , 生成一个文本文件 , 文本文件中包含了反编译的信息
打开生成的文本文件 , 在编译文件中可以看到 , 代码在执行的过程中 , 创建了一个 StringBuilder 对象进行拼接字符串 , 主要是在后面还调用了一个 toString() 方法
我们转到 StringBuilder 类实现文件下查看 , 发现 StringBuilder 类实现的 toString() 方法是返回一个 String 对象
而这个 String 对象正是 str6 变量引用的 , 在这个 String 对象中传入的参数正是 "He" 和 "llo" 拼接得到的 "Hello" , 所以 str6 并不是直接指向 字符串常量池 中的 Hello , 而是指向堆区中的 String 对象 , 再由这个 String 去指向 "Hello" , 所以和 str1 进行比较 , 比较的结果是 false
equals( )方法比较字符串
String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象 , Java中要想比较字符串内容的话 , 必须采用 String 类 提供的 equals 方法
public static void main(String[] args) {
String str1 = "Hello";
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
String str3 = "He" + "llo";
System.out.println(str1.equals(str3));
String str4 = "He";
String str5 = "llo";
String str6 = str4 + str5;
System.out.println(str1.equals(str6));
}
// 运行结果
true
true
true
equals 使用注意事项
如果一个 String变量 和 字符串常量 比较的话有两种方式
String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));
"Hello" 这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法 , 两种方式更推荐写成方式二 , 因为一旦 str 值为 null , 方式一 会抛出异常 , 而方式二能正常运行