JAVA中的String类

文章目录

  • 一、字符串常量池
  • 二、String底层逻辑
    • [2.1 private final byte coder](#2.1 private final byte coder)
  • 三、String方法
    • [3.1 求取字符串长度](#3.1 求取字符串长度)
    • [3.2 判断字符串是否为空](#3.2 判断字符串是否为空)
    • [3.3 获取指定索引位置的单个字符](#3.3 获取指定索引位置的单个字符)
    • [3.4 比较两个字符串的内容是否相等](#3.4 比较两个字符串的内容是否相等)
    • [3.5 查找字符第一次出现的下标](#3.5 查找字符第一次出现的下标)
    • [3.6 其他类型转成字符串](#3.6 其他类型转成字符串)
    • [3.7 字符串转成其他类型](#3.7 字符串转成其他类型)
    • [3.8 大小写转换](#3.8 大小写转换)
    • [3.9 字符串转数组](#3.9 字符串转数组)
    • [3.10 替换字符串](#3.10 替换字符串)
    • [3.11 字符串拆分](#3.11 字符串拆分)
    • [3.12 去除空格](#3.12 去除空格)
    • [3.13 字符串截取](#3.13 字符串截取)

一、字符串常量池

字符串常量池 = JAVA专门给字符串开的"缓存仓库",目的是少创建对象、省内存、跑的快。

  • 它是一块专门存放字符串字面量的内存区域
  • 所有用""双引号直接写的字符串,都会自动进池
  • 同一个内容的字符串,池中只存一份
  • 后面再用相同字符串,直接复用,不会再新建

总之:字符串常量池就是字符串对象的共享缓存区。

最直观的例子

java 复制代码
String s1 = "hello";
String s2 = "hello";

执行过程:

  1. 执行s1 = "hello";
    --->池中没有"hello"
    --->创建--->放入字符串常量池中
  2. 执行s2 = "hello";
    --->字符串常量池中已经有"hello"
    --->直接把池中对象地址给s2

结果

java 复制代码
System.out.println(s1 == s2); //true

==比较的是地址,此时s1和s2相等因为地址完全一样

那为什么new String不一样

new String String s = new String("abc");

  1. 先在堆中创建一个新 String 对象
  2. 再去常量池看 "abc" 是否存在
  3. 不存在则在字符串常量池中创建 "abc"
  4. 堆对象内部的 value 数组指向字符串常量池中的字符数组
java 复制代码
String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1 == s2); //false

原因:

  • new String(...)一定会在堆里创建新对象
  • 但双引号"hello"依然会进常量池
  • 所以s1、s2是堆上两个不同的对象
  • 只是它们的底层都指向池里同一个char[]

下面为打开断点进行调试可发现:

字符串常量放在哪?

  • Java 6及以前:在方法区(永久代PermGen)
  • Java 7+:移到了堆内存(Heap)里
  • Java 8+:永久代取消,用元空间MetaSpace,池仍在堆里

经典面试题:创建了几个对象

java 复制代码
String s = new String("abc");

答案:2个

  1. 堆中:new String对象
  2. 常量池:"abc"字符串对象

编译器优化

java 复制代码
String s = "a" + "b" + "c";

编译器会直接优化成:

java 复制代码
String s = "abc";

二、String底层逻辑

在 Java 9+ 的 String 类源码中,coder 和 hash 是两个非常关键的私有成员变量,分别负责编码标识和哈希码缓存.

2.1 private final byte coder

作用:标记底层 byte[] value 数组使用的字符编码格式,决定每个字符占 1 字节还是 2 字节。

背景(为什么引入 coder?)

  • JDK 8 及以前 :String 用 char[] 存储,固定 UTF-16 编码,每个字符占 2 字节
    问题:英文、数字等单字节字符(Latin-1)也占 2 字节,内存浪费 50%。
  • JDK 9+ 优化(Compact Strings) :改为 byte[] + coder,自动选编码
    • 纯 Latin-1 → 1 字节 / 字符
    • 含中文 / 特殊字符 → 2 字节 / 字符(UTF-16)

大幅节省内存(通常省 10%--15%)。

取值含义

java 复制代码
// String 类源码(JDK 9+)
static final byte LATIN1  = 0;
static final byte UTF16   = 1;

private final byte value[];  // 真实存储
private final byte coder;    // 编码标记
  • coder = 0 (LATIN1)
    字符串全是单字节字符 (a-z, 0-9, 英文符号)。
    每个字符占 1 字节。
    例:"Hello" → coder=0,value 长度 = 5。
  • coder = 1 (UTF16)
    中文、 emoji、生僻字 等非 Latin-1 字符。
    每个字符占 2 字节。
    例:"Hello你好" → coder=1,value 长度 = 14。

三、String方法

3.1 求取字符串长度

length()

  • 作用:获取字符串的字符个数(长度)
  • 返回值:int(空字符串返回 0)
java 复制代码
public static void main(String[] args) {
      String str = "Hello Java";
      System.out.println(str.length()); //输出:10(空格也算1个字符)

      String empty = "";
      System.out.println(empty.length()); //输出0
}

易错点:

  • 数组是 .length 属性,字符串是 .length() 方法,不能漏写括号!

3.2 判断字符串是否为空

isEmpty()

  • 作用:判断字符串是否为空字符串(length() == 0)
  • 返回值:boolean
java 复制代码
 public static void main1(String[] args) {
      String str1 = "";
      String str2 = " "; //空格字符串
      System.out.println(str1.isEmpty());
      System.out.println(str2.isEmpty()); //false(空格不是空)
}

易错点:

  • isEmpty() 只判断空字符串,不判断 null!
  • null.isEmpty() 会报 NullPointerException(空指针异常)。

3.3 获取指定索引位置的单个字符

charAt(int index)

  • 作用:获取指定索引位置的单个字符
  • 参数:index(索引从 0 开始)
  • 返回值:char
java 复制代码
public static void main2(String[] args) {
      String str = "abcdef";
      char c = str.charAt(1);//找到对应下标的字符
      System.out.println(c); //输出b
}

易错点:

  • 索引越界:index < 0 或 index >= length() → StringIndexOutOfBoundsException
  • 最后一个字符索引是 length()-1,不是 length()。

3.4 比较两个字符串的内容是否相等

  1. equals():严格区分大小写
java 复制代码
public static void main(String[] args) {
     String s1 = "abcd";
     String s2 = new String("abcd");

     // 正确:比较内容是否相同
     System.out.println(s1 == s2); //false
     // 错误:比较地址,结果 false
     System.out.println(s1.equals(s2)); //true
}

2.equalsIgnoreCase():忽略大小写比较

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

     //不忽略大小写
     System.out.println(s1.equals(s2));
     //忽略大小写
     System.out.println(s1.equalsIgnoreCase(s2)); //true
}

3.5 查找字符第一次出现的下标

java 复制代码
public static void main(String[] args) {
    String s1 = "abcda";
    System.out.println(s1.indexOf('a'));
}
  1. 查找指定字符在字符串中的位置,找到返回下标,没有找到返回-1。
  2. 如果在字符串中同时存在多个指定字符,那么从左到右进行查找,只会返回第一个字符所对应字符串的下标
java 复制代码
public static void main(String[] args) {
        String s1 = "abcda";
        System.out.println(s1.indexOf('a',2)); //4
    }

如果多了一个参数,第二个参数则代表从哪个下标处进行查找,上述代码则是从s1字符串下标为2处进行查找字符a。所以返回4

java 复制代码
public static void main(String[] args) {
    String s1 = "abcdabcdabcd";
    System.out.println(s1.indexOf("cda")); //2
  }

除此之外,该方法还有重载方法可以查找字符串,如果找到该字符串,则返回第一个字符在字符串对应的下标,如果全部遍历完没找到则返回-1

3.6 其他类型转成字符串

  1. String.valueOf ( ) 最推荐、最安全
    适用:int、long、float、double、boolean、char、对象都能转
    优点:不会报空指针(null 会转成 "null" 字符串)
java 复制代码
public static void main(String[] args) {
      int a = 10;
      Integer b = 20;
      double c = 9.99;
      boolean d = false;
      String A = String.valueOf(a);
      System.out.println(A);
      String B = String.valueOf(b);
      System.out.println(B);
      String C = String.valueOf(c);
      System.out.println(C);
      String D = String.valueOf(d);
      System.out.println(D);
 }
  1. 包装类.toString ( )
    适用:Integer、Long、Double、Boolean 等包装类型
java 复制代码
Integer i = 456;
String s = i.toString();  // "456"

Double d = 3.14;
String s2 = Double.toString(d); // "3.14"

⚠️ 注意:如果对象是 null,会报空指针异常

  1. 变量 + "" 最简单、最常用
    适用:任何类型,代码最短
java 复制代码
int a = 789;
String s = a + "";  // "789"

double b = 1.23;
String s2 = b + ""; // "1.23"

优点:写起来最快

缺点:底层会创建多余对象,性能略差(一般场景无所谓)

3.7 字符串转成其他类型

一、基本类型转换(字符串 → int /double/long /boolean 等)

统一规律:全部用对应包装类的 parseXXX() 静态方法

格式:

java 复制代码
包装类.parse基本类型(String 字符串)

1、String 转 int

java 复制代码
String str = "123";
int num = Integer.parseInt(str);

2、String 转 double

java 复制代码
double d = Double.parseDouble("3.14");

3、String 转 boolean

只有 true / false 能转,其他全部转 false

java 复制代码
boolean flag = Boolean.parseBoolean("true");

二、String 转 包装类型(Integer、Double...)

java 复制代码
// String 转 Integer
Integer i = Integer.valueOf("100");

// String 转 Double
Double d = Double.valueOf("3.14");

转换的字符串格式必须合法

比如 "123abc"、" 123"、空字符串 ""、null

调用 parseInt() 直接抛出 NumberFormatException 数字格式异常

3.8 大小写转换

  1. 全部转大写
    字符串.toUpperCase()
java 复制代码
String str = "HelloJava";
String upper = str.toUpperCase();
System.out.println(upper);  // HELLOJAVA
  1. 全部转小写
    字符串.toLowerCase()
java 复制代码
String str = "HelloJava";
String lower = str.toLowerCase();
System.out.println(lower);  // hellojava

注意点!!!

  1. String 是不可变的
    toUpperCase()、toLowerCase() 不会修改原字符串,只会返回一个新字符串,必须用变量接收。
java 复制代码
String s = "abc";
s.toUpperCase();
System.out.println(s); // 还是 abc!原字符串没变
  1. 只转换英文字母 a-z A-Z
    数字、汉字、符号、空格全部不改变
java 复制代码
String s = "Java123你好!@#";
System.out.println(s.toUpperCase()); // JAVA123你好!@#

3.9 字符串转数组

把字符串每一个字符,单独放进数组

方法:字符串.toCharArray()

java 复制代码
public class StringTest {
    public static void main(String[] args) {
        // 1. 字符串转字符数组
        String s1 = "abc123";
        char[] charArr = s1.toCharArray();
        System.out.println("字符数组:");
        for (char c : charArr) {
            System.out.print(c + " ");
        }

        System.out.println();

        // 2. 字符串转字符串数组(分割)
        String s2 = "苹果,香蕉,橘子";
        String[] strArr = s2.split(",");
        System.out.println("\n分割数组:");
        for (String s : strArr) {
            System.out.println(s);
        }

        // 3. 字符数组转回字符串
        String newStr = new String(charArr);
        System.out.println("\n字符数组转字符串:"+newStr);
    }
}

3.10 替换字符串

  1. 全部替换(最常用)replace()
    全部替换所有匹配的内容

格式:

java 复制代码
字符串.replace(旧内容, 新内容)
java 复制代码
public static void main(String[] args) {
      String str = "javajava123";
      String s = str.replace("java","C#");
      System.out.println(s);  //C#C#123
}

特点:

  1. 替换所有出现的旧字符串
  2. 支持字符、字符串替换
  3. 不会修改原字符串,返回新字符串
  4. 不是正则,普通文本替换,最安全
  1. 只替换第一个 replaceFirst()
    只替换第一次出现的内容,后面的不动
java 复制代码
public static void main(String[] args) {
      String str = "javajava123";
      String s = str.replaceFirst("java","C#");
      System.out.println(s);  //C#java123
}
  1. 替换所有(正则专用)replaceAll()
    按照正则表达式替换所有内容
java 复制代码
public static void main(String[] args) {
    // 把所有数字替换成C#
    String str = "javajava123";
    String s = str.replaceAll("\\d","C#");
    System.out.println(s);  //javajavaC#C#C#
}

注意!

  1. 所有替换方法都满足:
    String 不可变,原字符串不会被修改,必须用变量接收结果
java 复制代码
String s = "abc";
s.replace("a","b");
System.out.println(s); // 依然是 abc
  1. 替换内容不存在时,不会报错,直接返回原字符串

完整代码运行:

java 复制代码
public class StringReplace {
    public static void main(String[] args) {
        String str = "aabbaa123aa";

        // 1. 全部替换
        String s1 = str.replace("aa", "xx");
        System.out.println("replace全部替换:"+s1);

        // 2. 只替换第一个
        String s2 = str.replaceFirst("aa", "xx");
        System.out.println("replaceFirst第一个:"+s2);

        // 3. 正则全部替换(替换所有数字)
        String s3 = str.replaceAll("\\d","*");
        System.out.println("replaceAll正则替换:"+s3);
    }
}

3.11 字符串拆分

方法格式:

java 复制代码
1. 字符串.split("分隔符")
2. 字符串.split("分隔符", 限制个数)  //split 带两个参数(限制拆分个数)

作用:按照指定符号,把一个字符串切开,返回 String[] 字符串数组

java 复制代码
String str = "Java,MySQL,HTML,Python";
// 以逗号 , 拆分
String[] arr = str.split(",");

String s = "1,2,3,4,5";
// 最多只拆分 2 次,分成3段
String[] arr = s.split(",", 3);

拆分后数组:

{"Java","MySQL","HTML","Python"}

{"1","2","3,4,5"}

注意!!!

split() 的参数是正则表达式!

下面这些符号本身是正则关键字,直接写会拆分失败、报错:

. * + ? \ | () []

想要正常拆分这些符号,必须加转义 \

格式:\特殊符号

  1. 点 . 拆分(最常考)
java 复制代码
String s = "www.baidu.com";
// 错误写法:split(".")  完全拆分失败
// 正确写法
String[] arr = s.split("\\.");
  1. 竖线 | 拆分
java 复制代码
String s = "a|b|c";
String[] arr = s.split("\\|");
  1. 反斜杠 \
    Java 字符串里本身 \ 就要写 \\,所以一共要 4 个斜杠\\\\
java 复制代码
String s = "a\\b\\c";
String[] arr = s.split("\\\\");

3.12 去除空格

trim()

  1. 作用
    只去掉字符串 首尾 的空格
    中间的空格完全不动
java 复制代码
String s = "  hello java  ";
String res = s.trim();
System.out.println(res);
// 结果:hello java

缺点非常大:

  • 只能清前后空格
  • 中间空格删不掉
  • 只能删普通半角空格,全角空格(中文空格)删不掉

用正则去除所有空格 replaceAll + 正则

  1. 去除全部空格(前后 + 中间所有空格)
java 复制代码
String s = "  hello  java  ";
String res = s.replaceAll("\\s+", "");

\s 匹配任意空白(普通空格、制表符)

  • 匹配一个或多个,连续空格一次性删完
    结果:hellojava

3.13 字符串截取

Java 字符串截取就两个主力方法:

  1. substring(int beginIndex)
  2. substring(int beginIndex, int endIndex)
    还有辅助:charAt、split 分割截取。

重点规则:

Java 下标从 0 开始

区间规则:左包含,右不包含

start , end) 取 start,不取 end

第一种:substring (起始下标)

从指定下标,一直截取到字符串末尾

语法:

java 复制代码
String 新字符串 = 原字符串.substring(开始下标);

示例:

java 复制代码
String s = "abcdef";
// 下标:0:a 1:b 2:c 3:d 4:e 5:f

String res = s.substring(2);
System.out.println(res); 

输出:cdef。从下标 2 开始,后面全部拿走。

第二种:substring (开始下标,结束下标)

截取 [start, end) 中间一段

左取右不取!

语法:

java 复制代码
s.substring(start, end);

示例:

java 复制代码
String s = "abcdef";
String res = s.substring(1,4);
System.out.println(res);

下标:1=b,2=c,3=d,4=e(不取)

输出:bcd 包头不包尾,左闭右开

相关推荐
一只叫煤球的猫1 小时前
ThreadForge 1.2.0 发布:让 Java 并发代码更好写,这次补齐了高阶编排、示例与观测能力
java·设计模式·设计
counting money1 小时前
Spring框架基础(依赖注入-半注解形式)
java·后端·spring
CN-Dust1 小时前
【C++】for循环例题专题
java·c++·算法
染夕陌木1 小时前
RPC/服务调用框架中“方法无法应用到给定类型”错误的通用排查指南
java·ide·rpc
大大杰哥1 小时前
String常用方法
java
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第20题:HashMap在计算index的时候,为什么要对数组长度做减1操作
java·开发语言·数据结构·后端·面试·哈希算法·hash-index
嵌入式×边缘AI:打怪升级日志2 小时前
嵌入式Linux开发(了解交叉编译工具链的组成)
java·linux·运维
FreeGo~2 小时前
Linux 系统编程 进程篇 (五)
java·linux·服务器
XiYang-DING2 小时前
【Java EE】定时器
java·python·java-ee