java字符串和编码学习
1.8k7 分钟
原文链接:https://www.cnblogs.com/Time25/p/19903763.html
字符串是不可以变得
原因:
1.底层的逻辑实现
java的String类是不可以被子类继承的
内部数组被 final 和 private 修饰:
在 Java 8 及以前:private final char[] value;
在 Java 9 及以后:private final byte[] value;(为了节省空间,引入了 Compact Strings)
final 保证了数组的引用不能指向其他数组,private 保证了外部无法直接操作这个数组内部的元素,且 String 类内部没有提供任何可以修改该数组内容的方法(没有 Setter)。
除了这个之外还有别的原因
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 为什么这么设计?主要基于以下四个核心原因: 1. 实现字符串常量池(String Pool),节省内存 字符串是 Java 编程中使用最频繁的数据类型。为了避免每次都创建相同的字符串对象而消耗大量内存,JVM 提供了一块特殊的内存区域叫字符串常量池。 当代码中出现双引号包裹的字符串(如 String s1 = "hello";)时,JVM 会先去常量池里找。 如果已经存在 "hello",就直接把引用赋给 s1;如果不存在,再创建并放入池中。把这个的地址的值返回给对象 假设 String 是可变的: 如果 String s1 = "hello"; String s2 = "hello";,它们指向内存中同一个对象。一旦你通过 s1 把字符串改成了 "world",s2 的值也会莫名其妙地变成 "world"。 结论: 只有字符串不可变,字符串常量池这种“共享机制”才能安全地实现,从而大幅节省内存和提升性能。 2. 安全性(Security) String 在 Java 内部和外部交互中被广泛用作参数,比如: 网络连接(主机名和端口) 数据库连接(URL、用户名、密码) 文件系统读写(文件路径) 类加载机制(Classloader 加载类的全限定名) 假设 String 是可变的: 考虑如下场景,你的代码在打开一个重要文件前,先进行安全校验。 code Java String filePath = "/safe/directory/file.txt";
checkSecurity(filePath);
readFile(filePath); 如果字符串是可变的,即使通过了安全检查,在真正执行操作之前,它的值仍然可能被篡改。不可变性保证了参数传递时的绝对安全。 3. 线程安全(Thread Safety) 因为 String 对象的内容无法被修改,所以它是天生线程安全的。 在多线程环境下,无论多少个线程同时访问、读取同一个 String 对象,都不会发生数据不一致(竞态条件)的问题。这使得开发者在多线程编程时,不需要对 String 的读写进行加锁(synchronized)操作,从而简化了代码并提高了并发性能。 4. 完美契合 HashMap 的键(缓存 HashCode) 在 Java 中,我们非常频繁地使用 String 作为 HashMap、HashSet 等哈希集合的 Key。 哈希集合的运作极度依赖对象的 hashCode() 方法。如果一个对象的哈希值经常变动,它存入 HashMap 后,就再也找不到了(因为存放的“桶”位置不对了)。 因为 String 是不可变的,它的内容永远不会变,所以它的 HashCode 也就永远不会变。 性能优化: Java 在 String 类内部提供了一个变量 private int hash;,用来缓存哈希值。当第一次调用 hashCode() 时,它会计算并保存下来;以后再调用,直接返回缓存的值,速度极快。这使得 String 成为哈希表键的绝佳选择。
|
还有就是 String s=new String(“xxxxx”),这个跟String s=“xxxx”是不一样的,详细见https://javabetter.cn/string/constant-pool.html,这个是二哥的java进阶之路的教程
1 2 3 4 5 6 7 8 9 10
| public class Main { public static void main(String[] args) { String s1 = "hello"; String s2 = "HELLO".toLowerCase(); System.out.println(s1 == s2); System.out.println(s1.equals(s2)); } } 这段代码中,s2最后和s1一样了,那是不是代表s1==s2了呢,毕竟字符串常量池中就只有一个hello嘛,其实不是的,String s2 = "HELLO".toLowerCase();这个操作是在堆中创建了一个新的字符串
|
搜索字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| // 是否包含子串: "Hello".contains("ll"); // true //注意到contains()方法的参数是CharSequence而不是String,因为CharSequence是String实现的一个接口。 /* 这个是什么意思呢,CharSequence这个是一个接口,它规定了:“一个类如果想表示一段文字,必须具备哪些功能”(比如:能获取长度 length()、能按索引取字符 charAt()、能截取子串 subSequence())。 String StringBuilder StringBuffer都实现了这个接口 public boolean contains(CharSequence s)的意思是“只要你传给我的参数是一段连续的字符(不管你是 String、StringBuilder 还是 StringBuffer),我都能处理!” */
"Hello".indexOf("l"); // 2 "Hello".lastIndexOf("l"); // 3 "Hello".startsWith("He"); // true 是以这个开头的嘛 "Hello".endsWith("lo"); // true 是以这个结尾的嘛 "Hello".substring(2); // "llo" "Hello".substring(2, 4); "ll",//这个是停在下标为4的字符前面,但是不把这个加入进去,最终这个有几个字符,就是看后面一个数字减去前面一个数字 使用trim()方法可以移除字符串首尾空白字符。空白字符包括空格,\t,\r,\n: " \tHello\r\n ".trim(); // "Hello" 另一个strip()方法也可以移除字符串首尾空白字符。它和trim()不同的是,类似中文的空格字符\u3000也会被移除: "\u3000Hello\u3000".strip(); // "Hello" " Hello ".stripLeading(); // "Hello " " Hello ".stripTrailing(); // " Hello" String还提供了isEmpty()和isBlank()来判断字符串是否为空和空白字符串: "".isEmpty(); // true,因为字符串长度为0 " ".isEmpty(); // false,因为字符串长度不为0 " \n".isBlank(); // true,因为只包含空白字符 " Hello ".isBlank(); // false,因为包含非空白字
|
替换字符串
有两种方法。一种是根据字符或字符串替换:
1 2 3
| String s = "hello"; s.replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w' s.replace("ll", "~~"); // "he~~o",所有子串"ll"被替换为"~~"
|
第二种是正则表达式替换:
1 2
| String s = "A,,B;C ,D"; s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
|
分割字符串
1 2
| String s = "A,B,C,D"; String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
|
拼接字符串
1 2
| String[] arr = {"A", "B", "C"}; String s = String.join("***", arr); // "A***B***C"
|
格式化字符串
// TRAINING LOG · 第 56 / 76 篇训练记录 · 成文于 2026.04.21 · 夜间 21:01