String、StringBuilder类超详细笔记

  • Post author:
  • Post category:其他

String类

1. String类的声明

//jdk源码:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {

String类被final修饰,不允许被继承。即String类不存在子类,Java程序中有且仅有一个String类。

继承和方法重写在为程序带来灵活性的同时,也会带来多个子类行为不一致的问题。为保证所有JDK使用者都使用同一String类的固有实现,不允许自定义修改其中的具体实现,不希望体现个体差异性,故使用final终结器,防止String类被继承后进行方法重写。

2. 创建String字符串对象的四种方式

1)直接赋值:String str = "Hello BubbleoOoo"

2)构造方法中传入字符串:String str = new String("Hello BubbleoOoo");

3)构造方法中传入字符数组:将字符数组转为字符串

char[] data = new char[] {'a', 'b', 'c'}; String str = new String(data);

4)调用String类的静态方法valueOf(任意数据类型):将其他数据类型转为String类型

String str = String.valueOf(10);

3. String字符串的不可变性

一个String类字面量""即一个字符串对象,保存在字符串常量池中。

new出来的字符串对象保存在堆中。

String字符串为不可变字符串,字符串对象一旦声明后即不可修改。程序中修改的仅仅是String类的引用,修改其指向的字符串对象地址。

public class StringTest {
    public static void main(String[] args) {
        String str1 = "hello world";
        String str2 = str1;  //str2引用也指向"hello world"
        str2 = "hello";  //【仅修改str2引用指向的字符串对象,并未修改字符串对象本身,也不影响str1引用】
        System.out.println(str2);  //输出str2转为指向"hello"
        System.out.println(str1);  //输出str1仍然指向"hello world"
    }
}

e.g. 下方例题输出结果为 good and gbc 。

【题解】

1)输出gbc的原因:change方法的形参ch为数组对象的引用。当main方法中的ex.ch作为实参传递给change方法的形参ch时,这两个引用都指向同一个数组对象的地址值。而在执行change方法的过程中,ch[0] = 'g';通过形参引用ch改变了数组对象本身,故当再次通过实参引用ex.ch访问该数组时,访问到的是改变后的数组,即gbc。

2)输出good的原因:main方法中的ex.str在创建对象ex调用构造方法时完成初始化,指向堆中的”good”。当调用change方法时,会拷贝传入的实参引用ex.str的地址值,使得此时形参引用str也指向堆中的”good”。在执行change方法的过程中,形参引用str的指向发生了转变,转为指向常量池中的”test ok”,但原堆中对象”good”并未被改变。且实参的ex.str仍然指向原堆中的”good”。待change方法执行结束,其形参引用str随之销毁。

  • 当引用数据类型的引用作为方法形参时,根据参数的值传递,会将实参引用的地址值拷贝一份给形参引用,使得两个引用指向同一个地址值

  • 方法的形参、方法内的局部变量均为临时变量。当方法被调用入栈时,其形参、局部变量入栈,待方法调用结束出栈时,这些临时变量都会随之销毁。

public class Example{
    String str = new String("good");  
    char[] ch = { 'a' , 'b' , 'c' };  
    public static void main(String args[]){ 	 //【main方法入栈】
        Example ex = new Example();  			//【ex.str入栈、ex.ch入栈】
        //ex.str在创建对象调用构造方法时完成初始化,指向堆中的"good" 
        ex.change(ex.str,ex.ch); 				//【change方法入栈】【change方法的形参str、ch入栈】
        //change方法的形参str由实参ex.str值传递而来,初始时也指向堆中的"good"
        /* 执行change方法:改变形参str的指向为常量池中的"test ok",不影响堆中的"good"本身
           直接改变了形参ch引用指向的堆中的数组本身 */
      									 	 //【change方法及其形参出栈】
        //此时change方法的形参引用str、ch都随方法的调用结束而销毁
        System.out.print(ex.str + " and ");  
        //ex.str引用仍然指向堆中的"good",且"good"本身未发生改变
        System.out.print(ex.ch);
        //原实参ex.ch引用指向的数组本身已经发生了改变,访问到的即是改变后的数组gbc
    }										//【main方法出栈】
    public void change(String str,char ch[ ]){  //形参str引用初始时指向堆中的"good"
        str = "test ok";  //形参str引用转为指向常量池中的"test ok",但并非修改"good"对象本身
        ch[0] = 'g';  //通过形参ch引用已经改变了堆中的数组对象本身
    }
}

在这里插入图片描述
【总结】
注意当String引用作为实参通过值拷贝传递给形参时,形参和实参的String引用会指向同一个内存地址值。但当形参的Stiring引用发生改变时,只是改变了形参String引用的指向,不改变原String字符串对象(不可变性); 而原来实参的String引用仍然指向原地址值,即指向原String字符串对象。

String字符串不可变的原因

//jdk8源码:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final char[] value; 
    ...
}
//jdk17源码:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc {
    @Stable
    private final byte[] value; 
    ...
}

String字符串实际上底层是以字符数组value实现存储的,访问控制修饰符为private,类外无法直接访问,且String类并未为value数组提供相应public权限的getter和setter方法,故外部无法访问此属性,无法对此字符数组进行修改,即无法对此字符串进行修改。

修改字符串内容的两种方式

  1. 在运行时通过反射破坏value数组的封装(不推荐)
  2. 改用StringBuilder类或StringBuffer类

4. 字符串常量池

JDK8以前,字符串常量池在方法区中; JDK8以后,字符串常量池在堆中。

使用直接赋值法String str = "hello world";创建字符串对象时,会先在字符串常量池中检索是否已经存在一个”hello world”字符串常量。若不存在则创建一个新的,加入常量池中,并使str引用指向此新对象; 若常量池中已经存在此对象,则直接将str引用指向常量池中现有的字符串对象。

//以下代码只产生了1个字符串对象
String str3 = "hello world";
String str4 = "hello world";
String str5 = "hello world";
System.out.println(str3 == str4);  //输出true,指向的是同一个对象
System.out.println(str4 == str5);  //输出true,指向的是同一个对象
//以下代码产生了3个字符串对象:str6、str7指向的对象在堆区,"hello"在常量池中
String str6 = new String("hello");
String str7 = new String("hello");

5. String类的常用方法

1)equals()方法

String类重写了Object类的equals()方法,重写前默认比较的是对象的地址值,重写后比较的是对象的内容。

equals()方法比较两个字符串的内容,区分大小写。

【注意事项】使用equals()方法时尽量将字符串字面量写在前面"hello".equals(str) ,若写作str.equals("hello"),则当str为null时会引发空指针异常。

JDK常用类已经覆写了equals()方法,如String、Integer等,可直接比较内容。

2)equalsIgnoreCase()方法

equalsIgnoreCase()方法比较两个字符串的内容,不区分大小写。

3)intern()方法

intern()方法会将当前String类引用指向的对象保存到字符串常量池中。

//jdk源码:
public native String intern();

若常量池中已存在该字符串对象,则不再入池。返回常量池中此现有的字符串对象地址。

若常量池中不存在该字符串对象,则让该对象入池(移动到常量池中)。返回此对象入池后的地址。

e.g. (intern不入池的情况)

String str1 = new String("hello");
str1.intern();  //让堆区的"hello"入池,但池中已有一个"hello",不再入池
//【此时str1仍然指向堆区的"hello"】
String str2 = "hello";  //str2指向池中的"hello"
System.out.println(str1 == str2);  //输出false
String str1 = new String("hello");
str1 = str1.intern();  //intern()的返回值为常量池中现有的"hello"的地址
//【此时str1转为指向池中的"hello"】
String str2 = "hello";
System.out.println(str1 == str2);  //输出true

以上两段代码仅第2行不同,但输出结果相反。

e.g. (intern入池的情况)

char[] data = new char[] {'a','b','c'};
String str1 = new String(data);  //str1指向堆区的"abc"
str1.intern();  //常量池中不存在"abc",str1指向的"abc"入池(从堆区移动到常量池)
//【此时str1指向的"hello"位于常量池中】
String str2 = "abc";
System.out.println(str1 == str2);  //输出true

4)comparable()方法

String类也实现了Comparable接口,重写了compareTo()方法。按照“字典序”比较两个字符串的大小,返回二者ASCII码的差值。

字典序:按照字符的ASCII码值从小到大排序。如:A(65)排在a(97)前面。

String str1 = "abc";
String str2 = "Abc";
System.out.println(str1.compareTo(str2));  //输出32,【返回值int为str1-str2】 97(a)-65(A)=32

5)字符串的查找操作

public boolean contains(CharSequence s):判断该字符串的子序列是否存在

public boolean startsWith(String prefix):判断该字符串是否以指定字符串开头

public boolean endsWith(String suffix):判断该字符串是否以指定字符串结尾

String str = "hello world";
System.out.println(str.contains("world"));  //判断是否包含"world",输出true
System.out.println(str.startsWith("hello"));  //判断是否以"hello"开头,输出true
System.out.println(str.startsWith("ooo"));  //输出false
System.out.println(str.endsWith("world"));  //判断是否以"world"结尾,输出true
System.out.println(str.endsWith("ooo"));  //输出false

6)字符串的替换操作

public String replaceAll(String regex, String replacement):替换该字符串中所有指定的字符串内容

public String replaceFirst(String regex, String replacement):替换该字符串中第一个指定的字符串内容

String str = "hahahawaiwo";
System.out.println(str.replaceAll("ha", "01"));  //把所有ha都替换为01,输出010101waiwo
System.out.println(str.replaceFirst("ha", "01"));  //把第一个ha替换为01,输出01hahawaiwo

7)字符串的拆分操作

public String[] split(String regex):将一个字符串按照指定字符串作为间隔,全部拆分为多个字符串

public String[] split(String regex, int limit):将一个字符串按照指定字符串作为间隔,拆分成指定个数的字符串

String str = "hello java hello Rocket";
String[] strings1 = str.split(" ");  //将整个字符串按照空格拆分为多个字符串,保存在字符串数组中
String[] strings2 = str.split(" ", 2);  //将字符串按照空格拆分成两个字符串,保存在字符串数组中
System.out.println(Arrays.toString(strings1));  //输出[hello, java, hello, Rocket]
System.out.println(Arrays.toString(strings2));  //输出[hello, java hello Rocket]

若拆分的标准是特殊字符,则需要进行转义处理,否则无法拆分。

String s = "192.168.1.1";
String[] strings = s.split("\\.");  //特殊字符需要转义处理
System.out.println(Arrays.toString(strings));  //输出[192, 168, 1, 1]

8)字符串的截取操作

public String substring(int beginIndex):从指定索引处开始截取到字符串结尾

public String substring(int beginIndex, int endIndex):从指定索引处开始截取字符串到指定索引之前结束

Java中大多数范围均为左闭右开区间。

String str = "hello world";
System.out.println(str.substring(5));  //从索引为5处开始截取字符串到末尾,输出 world
System.out.println(str.substring(2, 7));  //截取[2,7)之间的字符串,输出llo w

9)其他常用方法

public String trim():去除字符串的首尾空格,保留中间的空格

public String toUpperCase():字符串转大写

public String toLowerCase():字符串转小写

public int length():获取字符串长度

public boolean isEmpty():判断字符串是否为空字符串【空字符串 ≠ null】

String str = " hello bubble ";
System.out.println(str.trim());  //输出hello bubble
System.out.println(str.toUpperCase());  //输出 HELLO BUBBLE
System.out.println(str.toLowerCase());  //输出 hello bubble 
System.out.println(str.length());  //输出14,包括空格
将字符串的首字母作大写处理
 public static void main(String[] args) {
     System.out.println(StringFirstUpper(""));  //空字符串,输出null
     System.out.println(StringFirstUpper("a"));  //输出A
     System.out.println(StringFirstUpper("hello"));  //输出Hello
 }

/** 将字符串的首字母大写处理 */
public static String StringFirstUpper(String str) {
    // 1.判空处理
    if(str == null || str.isEmpty()) {
        return null;
    }
    // 2.边界条件
    if(str.length() == 1) {  //字符串只有一个字符
        return str.toUpperCase();  //直接返回
    }
    //字符串中不止一个字符
    return str.substring(0,1).toUpperCase() + str.substring(1);
    //截取第一个字符转为大写后,截取原后续字符串进行拼接
}

6. String和char的相互转换

1)char转换为String

① 构造方法String(char[] value):将整个字符数组转为String字符串

char[] data = new char[] {'a','b','c'};
String str = new String(data);
System.out.println(str);  //输出abc

② 构造方法String(char[] value, int offset, int count):将部分字符数组转为String字符串

offset:开始转换为字符串的索引 count:转换为字符串的字符个数

char[] data = new char[] {'a','b','c','d','e'};
String str = new String(data, 1, 3);  //从索引1开始,转换3个字符为字符串
System.out.println(str);  //输出bcd

2)String转换为char

① String类的public char charAt(int index)方法:取出字符串中指定索引处的单个字符

String str = "hello";
System.out.println(str.charAt(1));  //输出e

② String类的public char[] toCharArray()方法:将整个字符串转为字符数组

String str = "hello";
char[] ch = str.toCharArray();
System.out.println(ch);  //输出hello
ch[2] = '7';  //修改字符数组
System.out.println(ch);  //输出he7lo
System.out.println(str);  //输出hello,原字符串并未被修改(String对象不可变)
判断一个字符串是否是纯数字字符串

先将整个字符串转为字符数组,再遍历检查每个字符是否都是数字,找到反例则返回false。

判断一个字符是否是数字:可使用包装类Character的静态方法isDigit(char c)

public static void main(String[] args) {
    String s1 = "abc";
    String s2 = "abc123";
    System.out.println(isDigitString(s1));  //输出true
    System.out.println(isDigitString(s2));  //输出false
}

/** 判断某个字符串是否是纯数字字符串 */
public static boolean isDigitString(String str) {
    //先将该字符串转换为字符数组
    char[] ch = str.toCharArray();
    //遍历字符数组,检查每个字符
    for(char c : ch) {
        //若存在一个字符不是数字,则返回false
        //if(c < '0' || c > '9') return false;
        if(Character.isDigit(c)) return false;
    }
    return true;
}

7. String和byte的相互转换

1)byte转换为String

① 构造方法String(byte[] bys):将整个字节数组转为String字符串,每个字节按照对应的ASCII码值转换为字符

byte[] data = new byte[] {97,98,99};
String str = new String(data);  //按照相应的ASCII码值转为字符串
System.out.println(str);  //输出abc

② 构造方法String(byte[] bytes, int offset, int length):将部分字节数组转为String字符串

offset:开始转换为字符串的索引 length:转换为字符串的字节个数

byte[] data = new byte[] {97,98,99,100,101};
String str = new String(data, 2, 3);  //从索引2开始,转换3个字节为字符串
System.out.println(str);  //输出cde

2)String转换为byte

① String类的public byte[] getBytes()方法:将字符串按照默认的编码格式转换为字节数组

② String类的public byte[] getBytes(String charsetName)方法:将字符串按照指定的编码格式转换为字节数组

String str = "你好中国";
byte[] bys1 = str.getBytes();  //IDEA默认编码格式为UTF-8
byte[] bys2 = str.getBytes("GBK");  //抛出UnsupportedEncodingException异常
System.out.println(Arrays.toString(bys1));  //输出[-28, -67, -96, -27, -91, -67, -28, -72, -83, -27, -101, -67]
System.out.println(Arrays.toString(bys2));  //输出[-60, -29, -70, -61, -42, -48, -71, -6]

UTF-8编码格式下,一个中文汉字占3个字节。

GBK编码格式下,一个中文汉字占2个字节。

StringBuilder类

可变字符串,StringBuilder对象的内容可以修改。

1. StringBuilder类的常用方法

1)append()方法

使用append()方法可实现字符串的拼接操作,返回拼接后的StringBuilder对象。

2)reverse()方法

使用reverse()方法可实现StringBuilder字符串的反转操作。

3)delete(int start, int end)方法

删除字符串中指定索引范围为 [start,end) 的所有内容。

Java中大多数范围均为左闭右开区间。

StringBuilder sb = new StringBuilder("hello");
sb.append("world");
sb.delete(5,8);  //删除索引范围为[5,8)内的所有内容,即从w开始删除到l之前
System.out.println(sb);  //输出hellold

4)insert(int start, 任意数据类型)方法

在索引start处开始插入任意数据类型的内容。插入内容的起始索引是start。

StringBuilder sb1 = new StringBuilder("hello");
sb1.insert(2,77);
System.out.println(sb1);  //输出he77llo
sb1.insert(3,"ooo");
System.out.println(sb1);  //输出he7ooo7llo

2. StringBuilder与String的相互转换

1)String转为StringBuilder

① 构造方法StringBuilder(String str)

StringBuilder sb = new StringBuilder("hello"); 

② 字符串拼接方法append(String str)

StringBuilder sb = new StringBuilder();
sb.append("123");

2)StringBuilder转为String

通过StringBuilder对象调用toString()方法

String str = sb.toString();

String、StringBuilder、StringBuffer的区别

  1. String为不可变字符串类,StringBuilder、StringBuffer为可变字符串类。
  2. StringBuilder类性能较高,但存在线程安全问题; StringBuffer类线程安全,但性能较差。

版权声明:本文为weixin_62756510原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。