String 字符串详解

悲伤于过去;
意淫于将来;
遗忘于现在。

1 String

1.1 定义

  1. String 表示字符串,使用一对" "引起来表示,Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
  2. String 是一个 final 类,一方面表示其是不可继承的;另一方面代表不可变的字符序列,String 具有增、删、改方法,但是使用这些方法产生的对象是一个新的对象,而非原对象本身
  3. String 的底层是用 char[] 数组来实现的。(事实上,大部分数据结构的底层都是利用数组或者链表实现)
  4. String 实现了 Serializable 接口,这表示字符串是支持序列化的,这与 IO 流的传输密切相关;同时也实现了 Comparable 接口,这表示 String 是可以比较大小的。

1.2 两种实例化方式的差异

String 是一个比较特殊的类,它的实例化不仅仅可以通过 new 的形式,还可以通过字面量定义的方式来实例化。

1.2.1 通过字面量定义的方式来实例化

@Test
public void test(){
    //字面量的定义方式:此时的s1和s2的数据声明在JVM方法区中的字符串常量池中
    String s1 = "sharm";
    String s2 = "luma";

    //比较s1和s2的地址值
    System.out.println(s1 == s2); // false
    
    s1 = "luma";
    System.out.println(s1 == s2); // true
}

方法区的字符串常量池中是不会存储相同内容的字符串的,如下图所示,让为 s1 赋值 luma 时,只是让 s1 重新指向了新的地址(luma 的地址),而非重新创建了一个地址。
在这里插入图片描述

1.2.2 通过 new + 构造器的方式来实例化

@Test
public void test(){

    //通过new + 构造器的方式:此时的s1和s2保存的是数据在堆空间中开辟空间以后对应的地址值,然后堆空间中保存的地址值是常量池中的地址值
    String s1 = new String("sharm");
    String s2 = new String("sharm");

    String s3 = "sharm";

    //比较s1和s2的地址值
    System.out.println(s1 == s2); // false
    System.out.println(s1 == s3); // false
}

在这里插入图片描述

1.2.3 案例

@Test
public void test(){
	String str1 = new String();
	str1 = "zzm";
	
	String str2 = new String("zzm");
	
	String str3 = "zzm";
	System.out.println(str1 == str3); // true
	System.out.println(str2 == str3); // false
}

以上的内存结构参照的是 hotspot 的 JVM 的内存管理,不同版本的 JVM 有所区别。

1.3 字符串的拼接

/*
1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量;
2. 只要其中有一个是变量,结果就在堆中;
3. 如果拼接的结果调用intern()方法,返回值就在常量池中.
 */
@Test
public void test(){
    String s1 = "hellosharm";
    String s2 = "sharm";
    String s3 = "hello" + s2;
    System.out.println(s1 == s3); //false

    String s4 = "hello" + "sharm";
    System.out.println(s1 == s4); //true


    //s5 代表常量
    final String s5 = "hello";
    String s6 = s5 + "sharm";
    System.out.println(s1 == s6);//true

    String s7 = s3.intern();
    System.out.println(s1 == s7);//true

}

1.4 String 类与其他结构之间的转换

1.4.1 String 与包装类之间的转换

/*
String 与基本数据类型、包装类之间的转换:基本数据类型一定要调用包装类来转换。
String --> 基本数据类型、包装类: 调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 --> String:  调用String重载的valueOf(xxx)
 */
@Test
public void test1(){
    String str1 = "2696126203";
    int num = Integer.parseInt(str1);

    // 下面这两种方法都可以将基本数据类型转换成 String 型,转换后都保存在堆里。
    String str2 = String.valueOf(num);
    String str3 = num + "";

    System.out.println(str1 == str3); // false
}

1.4.2 String 与字符数组之间的转换

/*
String 与 char[]之间的转换,String 的底层就是 char[]。
String --> char[]: 调用String的toCharArray()
char[] --> String: 调用String的构造器
 */
@Test
public void test2(){
    String str1 = "hellosharm";  //题目: a21cb3

    char[] charArray = str1.toCharArray();
    for (int i = 0; i < charArray.length; i++) {
        System.out.println(charArray[i]);
    }

    char[] arr = new char[]{'h','e','l','l','o'};
    String str2 = new String(arr);
    System.out.println(str2);
}

1.4.3 String 与字节数组之间的转换

/*
String 与 byte[]之间的转换
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器

编码:字符串 -->字节                (看得懂 --->看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)

说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
 */
@Test
public void test() throws UnsupportedEncodingException {
    String str1 = "abc123中国";
    byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
    System.out.println(Arrays.toString(bytes));

    byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
    System.out.println(Arrays.toString(gbks));

    System.out.println("******************");

    String str2 = new String(bytes);//使用默认的字符集,进行解码。
    System.out.println(str2);

    String str3 = new String(gbks);
    System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!

    String str4 = new String(gbks, "gbk");
    System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!
}

1.5 String 的常用方法

参考 JDK,阅读 JDK 是一种能力。

 

2 StringBuffer 和 StringBuilder

异同点
String不可变的字符序列;效率最低;底层使用char[]存储
StringBuffer可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

StringBuffer 和 StringBuilder 包括 String 中的大部分方法,并且由于它们是可变的字符序列,所以对字符串内容进行增删时,不会产生新的对象。

两者均有增删改查方法,当使用 append 和 insert 时,如果原来 char 数组长度不够,可以自动扩容。扩容时,底层并不是真的用来存放数据的数组扩容了,而是用新的数组进行了替换。

一言以蔽之,在实际开发中,用的最多的是 StringBuffer 和 StringBuilder,然后通过项目是否需要多线程来决定选择两者中的哪一个。如果是多线程,则使用线程安全的 StringBuffer,反之,则使用 StringBuilder。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值