☰
【Java】String
2026/10/7 2:49:00 网站建设 项目流程

String 原理


字符串构造

  1. String是 Java 里代表字符串的引用类型

  2. 4 种常见写法:
    //1. 使用字符串常量直接赋值 String s1="hello 2026"; System.out.println(s1); //2. new String 对象(在堆创建对象) String s2=new String("hello 2026"); System.out.println(s2); //3. 使用字符数组构造字符串 char[] array={'h','e','l','l','o',' ','2','0','2','6'}; System.out.println(array); //4. 使用字节数组构造对象 byte[] bytes={97,98,99,100}; String s4=new String(bytes); System.out.println(s4);
  3. String 源码核心
    public final class String implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc { @Stable private final byte[] value; // 底层字节数组,存储字符串内容 private final byte coder; // 编码标记:LATIN1 / UTF16 private int hash; // 缓存字符串的hash值,默认0 private boolean hashIsZero; // 标记hash计算后是否真的为0,避免重复计算 }

    重点:
    1)String被final修饰 → 类不能被继承
    2)value是private final byte[]:数组引用不可变,
    字符串一旦创建内容不可修改(不可变性)


字符串常量池 StringTable

  1. 字符串常量池本质是 JVM 里一个HashTable(哈希表),
    作用就是缓存字符串常量,复用字符串,避免重复创建相同字符串,
    节省内存、提升查找效率



  2. 重点:
    1)JDK6:常量池在永久代,永久代内存容易 OOM
    2)JDK7 重大改动:StringTable 从永久代移到堆;JDK8 继续保留在堆
    3)底层是 HashTable,HashTable 就是 key-value 结构,查找速度快
  3. 什么是 “池化技术”
    1)池:提前准备好一批资源放在一处,需要的时候直接拿,
    不用每次重新创建资源,提高效率
  4. 例子
    1)每次临时要钱:需要的时候再去申请,流程麻烦、速度慢(不使用池)
    2)一次性把一年生活费放到卡里,要用直接取:池化思想,资源提前备好,随取随用
  5. 其他池例子:
    1)数据库连接池:提前创建一批数据库连接放池子里,程序直接拿,
    不用每次新建连接
    2)线程池:提前创建线程,复用线程,避免频繁创建销毁线程开销
  6. 池化的共同目的:减少创建 / 销毁资源的开销,提升性能
  7. String s1 = "hello";

    执行逻辑:
    1) JVM 去StringTable(常量池 HashTable)查找有没有hello
    2) 存在:直接返回池内字符串的引用,复用
    3) 不存在:在常量池中创建hello字符串,存入哈希表,返回引用

  8. String s2 = new String("hello");

    1) 先去常量池找 "hello",没有就放入常量池
    2) 在堆内存新建一个 String 对象,对象内部 value 数组引用常量池的字符数据
    结论:s1 == s2 → false,s1 指向常量池对象,s2 指向堆上新对象

  9. 面试问题
    1)为什么 JDK7 把 StringTable 移到堆
    永久代内存空间小,而且永久代 GC 回收效率差,放永久代容易内存溢出;
    放到堆中,GC 可以正常回收不再使用的字符串常量
    2)StringTable 底层是什么
    哈希表 HashTable,哈希表好处:查询快

字符串内存存储


例1
  1. String str1="abc"; String str2="abc"; System.out.println(str1==str2); //true


  2. 栈:存放局部变量str1、str2,里面保存引用地址0x112233
    堆内的 StringTable 常量池:存放字符串字面量 abc
  3. 代码逻辑
    1) 执行str1 = "abc":JVM 去常量池查找abc,不存在 → 在常量池创建"abc",
    地址0x112233,栈中str1保存这个地址
    2) 执行str2 = "abc":去常量池查找,已经存在 "abc",不会新建,
    直接复用池里对象。栈中str2也保存地址0x112233
    3)str1 == str2:比较引用地址,两者地址完全一样,结果true
  4. 结论
    双引号直接赋值,优先走常量池,相同字面量会复用同一个对象
例2
  1. String str1=new String("abc"); String str2=new String("abc"); System.out.println(str1==str2); //false

  2. 代码逻辑
    1)第一次new String("abc")
    a)
    先处理双引号字面量"abc":去常量池查找,
    没有就把abc放进 StringTable(地址0x112233)
    b)new关键字,在堆上创建一个全新的 String 对象(地址0x556677),
    这个对象内部value数组引用常量池里的"abc"
    c)栈变量str1保存堆上新对象地址0x556677
    2)第二次new String("abc")
    a)
    abc常量池已经存在,不再重复创建常量
    b)new 又在堆上新建另一个 String 对象(地址0x998877),
    内部 value 依然指向常量池的abc
    c)栈变量str2保存这个新对象地址0x998877
    3)str1 == str2:比较栈里保存的堆对象地址,0x998877 ≠ 0x556677→false
  3. 重点
    1) 只要写new,一定会在堆中创建新 String 对象,哪怕内容相同
    2) 两个new String("abc"):字符串内容相同,但是堆上是两个独立对象,
    地址不同
    3) 常量池里的"abc"只会创建一次,被两个堆 String 对象的 value 共享
  4. 面试高频
  5. 踩坑点
    1)==比较的是引用地址,不是字符串内容
    2) 如果想比较字符串内容,要用str1.equals(str2),equals 比较字符内容

常用方法


String 对象比较

  1. ==运算符
    核心规则:
    基本数据类型:==比较变量里面存储的值是否相等
    引用数据类型(String 属于引用类型):==比较对象的地址,判断是不是同一个对象

    int a=10; int b=20; int c=10; System.out.println(a==b); //false 值不相等 System.out.println(a==c); // true 值相等 String s1=new String("hello"); String s2=new String("hello"); String s3=new String("world"); String s4=s1; System.out.println(s1==s2); //false 两个new出来是不同的对象,地址不同 System.out.println(s2==s3); //false 地址不同 System.out.println(s1==s4); //true s4引用s1,指向同一个对象

    重点:new String()每次都会在堆上创建新对象,
    所以哪怕内容一样,地址也不一样,==结果是 false

  2. equals()方法
    1)Object类原生equals()默认就是==比较地址
    2)String 重写了 equals:不再比较地址,逐个比较字符串里面的字符内容是否一样, 返回boolean(true/false)
    String s1=new String("hello"); String s2=new String("hello"); String s3=new String("world"); System.out.println(s1==s2); //false 地址不同 System.out.println(s1==s3); //false 地址不同 System.out.println(s1.equals(s2)); //true 二者字符内容相同、 System.out.println(s1.equals(s3)); //false 二者字符内容不同
    开发场景:判断两个字符串内容是否相等,用equals(),不要用==
  3. compareTo()方法(字典序比较,返回 int)
    1) 字典序:按照字符的 Unicode 编码大小依次比较
    2) 返回值规则:
    a' 从第一个字符开始对比,遇到不相等字符 → 返回两个字符编码差值
    b' 前面所有字符都相同 → 返回两个字符串长度差值
    a'' 返回0:两个字符串完全一样
    b'' 返回负数:当前字符串 < 参数字符串
    c'' 返回正数:当前字符串 > 参数字符串
    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,第2位 b比c小,差值为-1 System.out.println(s1.compareTo(s3)); //0,全部字符相同 System.out.println(s1.compareTo(s4)); //-3 前3个字符相同,长度差值为3
  4. compareToIgnoreCase()
    1)逻辑和compareTo()完全一致,忽略大小写进行字典序比较
    String s1=new String("abc"); String s2=new String("ac"); String s3=new String("ABC"); String s4=new String("abcdef"); System.out.println(s1.compareToIgnoreCase(s2)); //-1,第2位,c和b字符编码差值为1 System.out.println(s1.compareToIgnoreCase(s3)); //0,忽略大小写相等 System.out.println(s1.compareToIgnoreCase(s4)); //-3 前3个字符相同,二者长度差值为3


String 字符串查找

  1. 方法清单


  2. String s="aaabbbcccaaabbbccc"; //获取下标3位置的字符 System.out.println(s.charAt(3)); // b //从字符串开头往后找,返回字符c第一次出现的下标 System.out.println(s.indexOf('c')); // 6 //从下标10的位置开始往后查找,返回字符c 第一次出现的下标 System.out.println(s.indexOf('c',10)); //15 //从开头向后查找子串bbb 返回子串bbb的起始下标 System.out.println(s.indexOf("bbb",10)); //12 //从字符串末尾向前查找,返回字符 c 最后一次出现的下标 System.out.println(s.lastIndexOf('c')); //17 //在下标10 以及他之前的范围,从后往前查找字符 c System.out.println(s.lastIndexOf('c',10)); // 8 //从末尾向前查找子串bbb 返回子串的起始下标 System.out.println(s.lastIndexOf("bbb")); // 12 // 在下标10以及它之前的范围,从后往前查找子串bbb System.out.println(s.lastIndexOf("bbb",10)); //3

  3. 1)indexOf:正向查找(左→右),找第一次出现
    2)lastIndexOf:反向查找(右→左),找最后一次出现
  4. 带第二个参数fromIndex:
    1)indexOf:从 fromIndex 位置向右搜索
    2)lastIndexOf:从 fromIndex 位置向左搜索
  5. 注意
    1)所有查找方法找不到目标时,统一返回-1
    2)charAt 如果下标超出范围,不是返回 - 1,而是抛出异常

转换


数值和字符串相互转换
  1. 数字 / 对象 → 字符串:String.valueOf()
    1)String.valueOf(任意类型):可以把数字、布尔、对象全部转为字符串
    2)对象调用 valueOf 时,底层会自动调用对象的 toString() 方法
    如果 Student 类没有重写 toString (),会输出对象地址;重写后输出自定义内容
    3)

    public class Student { String name; int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student : "+this.name+" "+this.age; } }
    public static void main(String[] args) { //数字转字符串 String s1=String.valueOf(1234); String s2=String.valueOf(12.34); String s3=String.valueOf(true); String s4=String.valueOf(new Student("zhangsan",16)); System.out.println(s1); System.out.println(s2); System.out.println(s3); System.out.println(s4); }
  2. 字符串 → 数值:包装类的 parseXxx()
    1)规则:
    a)Integer.parseInt(字符串):字符串必须是合法数字,不能带字母,
    否则报错NumberFormatException b)Double.parseDouble(字符串):解析浮点数字符串

    2)

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

    Integer、Double 是基本数据类型对应的包装类

  3. 重要坑点
    1)字符串转数字时,字符串内容必须是纯数字

    Integer.parseInt("12a3"); // 编译报错

    2)String.valueOf() 几乎万能,支持所有基本类型和对象转字符串
    3)区分两个方法:
    a)parseInt:字符串 → int
    b)String.valueOf:任意类型 → String


String 大小写转换
  1. 核心方法
    1)字符串.toUpperCase():小写 → 大写
    2)字符串.toLowerCase():大写 → 小写
    3)Java 中 String 是不可变字符串:
    调用这两个方法,不会修改原来的字符串,会返回一个新字符串

  2. String s1="hello"; String s2="HELLO"; //小写转大写 System.out.println(s1.toUpperCase()); //HELLO //大写转小写 System.out.println(s2.toLowerCase()); //hello
  3. 重点
    1)String 一旦创建,内容不能修改,toUpperCase() 只是生成新字符串,保留结果, 得用变量接收
    String s="abc"; s.toUpperCase(); System.out.println(s); // abc 小写转大写,原字符串没变 String s2=s.toUpperCase(); System.out.println(s2); //ABC
  4. 补充
    1)字符串里面的数字、符号不受影响,只会转换英文字母
    String str="hello123!"; System.out.println(str.toUpperCase()); // HELLO123!

字符串与字符数组互转
  1. 核心方法
    1)字符串 → char 字符数组:字符串.toCharArray()
    2)char 字符数组 → 字符串:new String(字符数组)

  2. String s="hello"; //字符串转字符串数组 char[] ch=s.toCharArray(); for(int i=0;i<ch.length;i++) { System.out.print(ch[i]); // hello } System.out.println(); //字符数组转回字符串 String s2=new String(ch); System.out.println(s2); //hello
  3. 重点
    1)toCharArray()返回新的字符数组,原字符串不会改变(String 不可变)
    2)new String(char[]):根据字符数组创建新字符串
    3)用途场景:需要修改字符串里单个字符的时候,可以先转成 char 数组,
    修改数组元素,再转回字符串
  4. 例:修改字符串的第 0 个字符
    //字符串转字符数组 --> 修改元素后,字符数组转字符串 String s1="hello"; char[] arr=s1.toCharArray(); arr[0]='H'; for(int i=0;i<arr.length;i++) { System.out.print(arr[i]+" "); } //H e l l o System.out.println(); String newStr=new String(arr); System.out.println(newStr); // Hello

    toCharArray():把字符串里的每一个字符,提取出来存入char[]数组


String.format () 字符串格式化
  1. 常用占位符
    1)%d:整数(decimal)
    2)%s:字符串(string)
    3)%f:浮点数(float)
    4)%c:单个字符(char)

  2. 语法
    String.format(格式字符串, 参数1, 参数2, 参数3...)

    作用:按照指定模板,拼接生成新字符串

  3. String s=String.format("%d-%d-%d",2026,10,6); System.out.println(s); //%s 字符串占位 String name="张三"; String str1=String.format("姓名:%s",name); System.out.println(str1); // %f 浮点数 .2f代表保留2位小数 double score=95.678; String str2=String.format("成绩 %.2f",score); System.out.println(str2); //组合使用 String str3=String.format("姓名:%s , 年龄:%d , 性别:%s , 学号:%s ", "张三",21,"男","2026106"); System.out.println(str3);
  4. 考点
    1)String.format() 返回新字符串,原字符串不受影响
    2)占位符数量、类型必须和后面参数匹配,否则报错
    String.format("%d", "abc"); // 编译报错,类型没和后面传入的参数类型匹配

    3)和直接+字符串拼接相比:模板更清晰,适合日期、报表等格式化输出

字符串替换

  1. // 字符串替换 String str="hello world"; System.out.println(str.replaceAll("l","_")); // he__o wor_d System.out.println(str.replaceFirst("l","_")); // he_lo world
  2. replace(CharSequence target, CharSequence replacement),
    它不使用正则,只是纯文本替换
  3. 重点
    1)String 不可变
    替换方法不会修改原来的字符串,返回的是全新字符串
    String str="hello world"; str.replaceAll("l","_"); System.out.println(str); //hello world ,字符串替换后,原字符串不会改变
    如果想保存结果,需要用变量接收:
    String str="hello world"; str.replaceAll("l","_"); System.out.println(str); //hello world ,字符串替换后,原字符串不会改变 String newStr=str.replaceAll("l","_"); System.out.println(newStr); //he__o wor_d

    2)replaceAll和replaceFirst的第一个参数是正则表达式
    坑举例:.在正则里代表任意字符
    String s="a.b.c"; System.out.println(s.replaceAll(".","#")); // 输出 ##### . 匹配了所有字符,不是匹配 . 字符 // 匹配文本 . 需要转义 System.out.println(s.replaceAll("\\.","#")); // a#b#c
    a) 匹配文本.,需要转义:replaceAll("\\.", "#")
    b) 如果只是想做普通文本替换,不想碰正则,优先使用 replace("原文本","新文本")
    System.out.println(s.replace("\\.","#")); // a#b#c
  4. 小结
    1)replaceAll:全部替换,支持正则
    2)replaceFirst:只替换第一个,支持正则
    3)replace:全部替换,纯文本,不支持正则

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询