【Java SE】字符串 - String类

目录

本节学习目标

[1 String 类介绍](#1 String 类介绍)

[2 字符串对象的构造](#2 字符串对象的构造)

[3 字符串常量池 StringTable](#3 字符串常量池 StringTable)

代码对比理解==

[intern () 方法](#intern () 方法)

[4 字符串比较 API](#4 字符串比较 API)

[4.1 equals () 比较内容是否相等](#4.1 equals () 比较内容是否相等)

[4.2 compareTo () 字典序比较](#4.2 compareTo () 字典序比较)

[4.3 compareToIgnoreCase () 忽略大小写字典序比较](#4.3 compareToIgnoreCase () 忽略大小写字典序比较)

[5 String 常用工具方法](#5 String 常用工具方法)

[5.1 字符串和基本类型互相转换](#5.1 字符串和基本类型互相转换)

[5.2 大小写转换](#5.2 大小写转换)

[5.3 字符串与字符数组互相转换](#5.3 字符串与字符数组互相转换)

[5.4 split 字符串拆分(高频笔试考点)](#5.4 split 字符串拆分(高频笔试考点))

[5.5 substring 字符串截取(前闭后开!)](#5.5 substring 字符串截取(前闭后开!))

[5.6 trim () 去除字符串左右两边空格,中间空格保留](#5.6 trim () 去除字符串左右两边空格,中间空格保留)

[6 字符串不可变性(核心重点面试必考)](#6 字符串不可变性(核心重点面试必考))

[7 StringBuilder 和 StringBuffer](#7 StringBuilder 和 StringBuffer)

[StringBuilder 常用 API](#StringBuilder 常用 API)

[String 和 StringBuilder 互相转换](#String 和 StringBuilder 互相转换)

[StringBuilder vs StringBuffer 对比](#StringBuilder vs StringBuffer 对比)

[8 经典 OJ 练习题](#8 经典 OJ 练习题)

[习题 1:字符串中第一个只出现一次的字符(LeetCode387)](#习题 1:字符串中第一个只出现一次的字符(LeetCode387))

[习题 2:最后一个单词的长度(牛客)](#习题 2:最后一个单词的长度(牛客))

[习题 3:验证回文串 LeetCode125](#习题 3:验证回文串 LeetCode125)

📔本篇核心考点总结


本节学习目标

  1. 认识String字符串类,掌握多种字符串构造方式
  2. 理解字符串常量池 StringTable ,分清==equals()
  3. 熟练掌握 String 常用 API:比较、转换、拆分、截取、去空格、intern()
  4. 深刻理解字符串不可变性底层原理
  5. 分清StringBuilderStringBuffer,适用场景、线程安全区别
  6. 完成字符串经典 OJ 习题

1 String 类介绍

C 语言使用char[]字符数组处理字符串,数据和处理函数分开,不符合面向对象思想 。 Java 专门提供String引用类型,把字符串数据和操作方法封装到一起,开发中字符串使用极其频繁。

Stringfinal修饰的类,不能被继承

Java String源码

2 字符串对象的构造

常用 4 种构造方式:

java 复制代码
public static void main(String[] args) {
    //方式1:字符串字面量直接赋值(最常用)
    String s1 = "hello bit";
    System.out.println(s1);

    //方式2:new String,传入字符串字面量
    String s2 = new String("hello bit");
    System.out.println(s2);

    //方式3:字符数组构造字符串
    char[] array = {'h','e','l','l','o','b','i','t'};
    String s3 = new String(array);
    System.out.println(s3);

    //方式4:字节数组构造字符串
    byte[] bytes = {97,98,99,100}; //对应a b c d ASCII
    String s4 = new String(bytes);
    System.out.println(s4);
}

其他构造方法查阅官方文档:java.lang.String

3 字符串常量池 StringTable

字符串常量池底层本质是HashTable 哈希表 ,存字符串实例引用,目的:复用字符串对象,减少内存重复开销

规则:

  1. 使用双引号字面量"abc"创建字符串,JVM 优先去字符串常量池查找;
    • 如果池中已经存在"abc",直接复用池中的对象引用;
    • 如果池中不存在,则把字符串放入常量池。
  2. new String("abc"):一定会在堆上创建全新 String 对象,不直接复用池引用。

代码对比理解==

⚠️==对于引用类型比较的是对象地址,不是字符串内容

java 复制代码
public static void main(String[] args) {

    String str1 = "abc";
    String str2 = "abc";
    System.out.println(str1 == str2); // true,两个引用都指向常量池同一个对象

}
java 复制代码
public static void main(String[] args) {

    String str1 = new String("abc");
    String str2 = new String("abc");
    System.out.println(str1 == str2); // false,new出来是堆上两个不同对象
}

内存图解:

  • "abc"字面量存放在字符串常量池;
  • str1str2保存常量池对象地址;
  • new String("abc")在堆开辟新对象,内部 value 字符数组复用池里面内容,但对象本身是新堆实例。

intern () 方法

intern()作用:主动把当前字符串对象,放到 / 查找字符串常量池。

执行逻辑:

  1. 如果常量池已经存在 equals 相等的字符串,返回常量池中的引用
  2. 如果常量池没有,就把当前对象引用加入常量池,返回池内引用。
java 复制代码
public static void main(String[] args) {
    char[] ch = {'a','b','c'};
    String s1 = new String(ch); //堆上对象,不在常量池
    // s1.intern(); //打开这行,s1引用进入常量池
    String s2 = "abc"; //字面量,取自常量池
    System.out.println(s1 == s2); //false

    //打开 s1.intern();之后
    // System.out.println(s1.intern() == s2); //true
}

s1.intern(); //打开这行,s1引用进入常量池

面试高频:intern()一般用来手动把堆上字符串放入常量池,实现对象复用。

4 字符串比较 API

4.1 equals () 比较内容是否相等

String重写了 Object 的equals()按字符内容逐个比较

注意:==比较地址;业务判断字符串内容相等,必须用 equals,不要用 ==

java 复制代码
public static void main(String[] args) {
    String s1 = new String("hello");
    String s2 = new String("hello");
    String s3 = new String("Hello");

    System.out.println(s1 == s2);      //false 比较地址
    System.out.println(s1.equals(s2)); //true 比较内容
    System.out.println(s1.equals(s3)); //false 大小写不同
}

4.2 compareTo () 字典序比较

字典序:按照字符的 Unicode 编码大小比较。 返回值规则:

  1. 遇到第一个不相同字符,返回两个字符的差值;
  2. 如果前面字符全部一样,返回两个字符串长度的差值
java 复制代码
public static void main(String[] args) {
    String s1 = new String("abc");
    String s2 = new String("ac");
    String s3 = new String("abc");
    String s4 = new String("abcdef");

    System.out.println(s1.compareTo(s2)); //-1,b和c字符差
    System.out.println(s1.compareTo(s3)); //0,完全相同
    System.out.println(s1.compareTo(s4)); //-3,长度差
}

4.3 compareToIgnoreCase () 忽略大小写字典序比较

java 复制代码
public static void main(String[] args) {
    String s1 = new String("abc");
    String s3 = new String("ABc");
    System.out.println(s1.compareToIgnoreCase(s3)); //0 忽略大小写相等
}

5 String 常用工具方法

5.1 字符串和基本类型互相转换

  1. 其他类型 → String:String.valueOf(数据)
  2. String → 基本类型:包装类的parseXXX(),例如Integer.parseInt()Double.parseDouble()
java 复制代码
public static void main(String[] args) {
    //基本类型转字符串
    String s1 = String.valueOf(1234);
    String s2 = String.valueOf(12.34);
    String s3 = String.valueOf(true);
    System.out.println(s1);
    System.out.println(s2);
    System.out.println(s3);

    //字符串转基本类型
    int data1 = Integer.parseInt("1234");
    double data2 = Double.parseDouble("12.34");
    System.out.println(data1);
    System.out.println(data2);
}

5.2 大小写转换

⚠️注意:返回新字符串!原字符串不会被修改,String 不可变

java 复制代码
public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "HELLO";
    System.out.println(s1.toUpperCase()); //转大写
    System.out.println(s2.toLowerCase()); //转小写
}

5.3 字符串与字符数组互相转换

java 复制代码
public static void main(String[] args) {
    String s = "hello";
    //字符串 → char数组
    char[] ch = s.toCharArray();
    for (int i = 0; i < ch.length; i++) {
        System.out.print(ch[i]);
    }
    System.out.println();

    //字符数组转回字符串
    String s2 = new String(ch);
    System.out.println(s2);
}

5.4 split 字符串拆分(高频笔试考点)

String[] split(String regex):按照分隔符把字符串切割,返回字符串数组。 重载:split(regex, limit),limit 代表分割最多生成几份。

⚠️坑:. * + |属于正则特殊字符,不能直接写,需要转义\\

示例 1:普通空格分割

java 复制代码
String str = "hello world hello bit" ;
String[] result = str.split(" ") ;
for(String s: result) {
    System.out.println(s);
}

示例 2:限定分割份数

java 复制代码
String str = "hello world hello bit" ;
String[] result = str.split(" ",2) ;
for(String s: result) {
    System.out.println(s);
}

示例 3:分割 IP 地址,.要写\\.

java 复制代码
String str = "192.168.1.1" ;
String[] result = str.split("\\.") ;
for(String s: result) {
    System.out.println(s);
}

转义规则记忆:

  1. . * + |正则特殊符号,Java 字符串写\\符号
  2. 如果要表示反斜杠\,Java 字符串需要写\\\\

示例 4:多个分隔符,用|代表或

java 复制代码
String str = "name=zhangsan&age=18" ;
String[] result = str.split("&") ;
for (int i = 0; i < result.length; i++) {
    String[] temp = result[i].split("=") ;
    System.out.println(temp[0]+" = "+temp[1]);
}

5.5 substring 字符串截取(前闭后开!)

  • substring(beginIndex):从下标截取到末尾
  • substring(beginIndex, endIndex)[begin, end),包含起始下标,不包含结束下标
java 复制代码
String str = "helloworld" ;
System.out.println(str.substring(5));
System.out.println(str.substring(0, 5)); //取0~4下标字符

5.6 trim () 去除字符串左右两边空格,中间空格保留

java 复制代码
String str = " hello world " ;
System.out.println("["+str+"]");
System.out.println("["+str.trim()+"]");

6 字符串不可变性(核心重点面试必考)

String对象内容一旦创建,不能修改,属于不可变对象。

底层原理:

  1. String 内部存储字符的value字节数组被private final修饰;
  2. private:外部拿不到这个数组引用;
  3. final:数组引用不能指向别的数组。

❗所有看起来 "修改字符串" 的方法:toUpperCasesubstringsplit+拼接都不会改动原来字符串,而是返回全新 String 对象

字符串直接+拼接,会产生大量中间临时对象,性能很差。

java 复制代码
//不推荐,循环拼接String,产生大量垃圾对象
String s = "";
for(int i = 0; i < 10000; i++){
    s += i;
}

如果业务需要频繁修改字符串内容,不要直接用 String,使用StringBuilder / StringBuffer

7 StringBuilder 和 StringBuffer

String不可变;StringBuilderStringBuffer是可变字符串,内部数组内容可以直接修改,不会大量生成新对象。

StringBuilder 常用 API

java 复制代码
public static void main (String[] args) {
    StringBuilder sb1 = new StringBuilder ("hello");
    StringBuilder sb2 = sb1;

    sb1.append(' ');         //追加字符
    sb1.append("world");     //追加字符串
    sb1.append(123);         //追加数字
    System.out.println(sb1); // hello world123

    System.out.println(sb1 == sb2); //true,同一个对象引用
    System.out.println(sb1.charAt(0)); //获取指定下标字符 h
    System.out.println(sb1.length()); //有效字符长度
    System.out.println(sb1.capacity()); //底层数组总容量

    sb1.deleteCharAt(0);    //删除指定下标字符
    sb1.delete(0,5);        //删除区间 [0,5)

    sb1.reverse();          //字符串反转

    // StringBuilder转回String
    String str = sb1.toString();
    System.out.println(str);
}

String 和 StringBuilder 互相转换

  1. String → StringBuilder:new StringBuilder(字符串) 或者append()
  2. StringBuilder → String:调用.toString()

StringBuilder vs StringBuffer 对比

表格

线程安全 性能 适用场景
StringBuilder ❌非线程安全 单线程,绝大多数业务场景
StringBuffer ✅synchronized 同步,线程安全 较低 多线程并发场景下使用

开发绝大多数单线程业务优先StringBuilder

8 经典 OJ 练习题

习题 1:字符串中第一个只出现一次的字符(LeetCode387)

java 复制代码
class Solution {
    public int firstUniqChar(String s) {
        int[] count = new int[256];
        //统计每个字符出现次数
        for(int i = 0; i < s.length(); ++i){
            count[s.charAt(i)]++;
        }
        //遍历找第一个次数等于1
        for(int i = 0; i < s.length(); ++i){
            if(1 == count[s.charAt(i)]){
                return i;
            }
        }
        return -1;
    }
}

习题 2:最后一个单词的长度(牛客)

java 复制代码
import java.util.Scanner;
public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        while(sc.hasNext()){
            String s = sc.nextLine();
            //找到最后一个空格,截取后面部分求长度
            int len = s.substring(s.lastIndexOf(' ')+1, s.length()).length();
            System.out.println(len);
        }
        sc.close();
    }
}

习题 3:验证回文串 LeetCode125

java 复制代码
class Solution {
    public static boolean isValidChar(char ch){
        if((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')){
            return true;
        }
        return false;
    }
    public boolean isPalindrome(String s) {
        s = s.toLowerCase();
        int left = 0, right = s.length()-1;
        while(left < right){
            //左指针找有效字符
            while(left < right && !isValidChar(s.charAt(left))){
                left++;
            }
            //右指针找有效字符
            while(left < right && !isValidChar(s.charAt(right))){
                right--;
            }
            if(s.charAt(left) != s.charAt(right)){
                return false;
            }else{
                left++;
                right--;
            }
        }
        return true;
    }
}

📔本篇核心考点总结

  1. String 是引用类型,==比较地址;判断字符串内容相等必须使用equals()
  2. 字符串常量池StringTable:字面量会复用池对象;new String()堆上新对象;intern()主动入池。
  3. String不可变:所有修改操作返回全新字符串;循环拼接不要直接用 String,会产生大量临时对象。
  4. split 拆分注意正则特殊符号. * + |,需要转义\\;substring 是前闭后开区间。
  5. StringBuilder可变字符串,单线程高性能;StringBuffer加同步锁,线程安全,性能差。
  6. 转换:valueOf()其他类型转字符串;parseXXX()字符串转基础类型;toCharArray()字符串转字符数组。

面试简答 Q:为什么 String 设计成不可变?

A:①字符串常量池复用;②哈希缓存安全;③线程安全;④作为 HashMap 的 key 安全。

Q:StringBuilder 和 StringBuffer 区别?

A:StringBuilder 非线程安全,速度快,单线程推荐;StringBuffer 加 synchronized 同步,线程安全,性能低,适合多线程。
Q:String s = new String("abc");创建几个对象?

A:如果常量池没有"abc"2 个对象:常量池字符串对象 + 堆上 new 出来的 String 对象;如果池中已有,则只创建堆上 1 个对象

相关推荐
mldong7 小时前
给若依加审批流,不用 Flowable
java·spring boot·架构
Wang's Blog8 小时前
Java框架快速入门: Spring Security+OAuth2之数据库和实体类的RBAC改造
java·数据库·spring
讳疾忌医丶9 小时前
深度拆解 RocksDB 内核:基于 C++17 的 FIFO 调度状态机与温度阶梯自愈设计
java·c++·算法·架构
lsswear9 小时前
Python 并发 线程
开发语言·python
郑州光合科技余经理9 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
毅炼9 小时前
Rover-Suite 开源发布:轻量服务注册中心与网关一体化方案
java·后端·系统架构·gateway
软件课代表CiCi9 小时前
运行库是什么?电脑缺运行库怎么办?c++运行库缺失修复全攻略
开发语言·c++·windows·电脑·dll修复·dll丢失·运行库
梦想平凡10 小时前
百游棋牌源代码开发搭建教程(十):隔离部署、备份恢复与双端验收
java·前端·javascript·数据库·源代码管理
Ivanqhz10 小时前
MLIR OpBuilder
开发语言·python·mlir