String的不可变性
String就像刻在石头上的字,一旦创建就改不了 了
你以为你改了字符串?其实是Java偷偷创建了一个新的字符串对象,原来那个还在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testImmutable () {String s = "hello" ;System.out.println("原始s的地址:" + System.identityHashCode(s)); s = s + " world" ; System.out.println("拼接后s的地址:" + System.identityHashCode(s)); String a = "abc" ;String b = a;a = "xyz" ; System.out.println("b = " + b); }
为什么设计成不可变?
安全 :字符串常用于密码、网络连接、文件路径,如果能随意修改就太危险了
性能 :不可变才能放进字符串池复用,多个变量可以安全地指向同一个对象
线程安全 :不可变天生就是线程安全的,多个线程同时读也不怕
HashMap的key :String经常做HashMap的key,不可变保证hashCode不会变
字符串池(String Pool)
字符串池就像一个公共仓库 ,相同内容的字符串只存一份,大家共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void testStringPool () {String s1 = "hello" ;String s2 = "hello" ;System.out.println(s1 == s2); String s3 = new String ("hello" );String s4 = new String ("hello" );System.out.println(s3 == s4); System.out.println(s3.equals(s4)); String s5 = s3.intern();System.out.println(s1 == s5); }
面试经典:new String("abc") 创建了几个对象?
1个或2个
如果池中没有”abc”:先在池中创建一个,再在堆上new一个 → 2个
如果池中已有”abc”:只在堆上new一个 → 1个
== vs equals()
这是Java最容易踩的坑之一,参考 运算符 中关系运算符部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testEquality () {String a = "hello" ;String b = "hello" ;String c = new String ("hello" );System.out.println(a == b); System.out.println(a == c); System.out.println(a.equals(c)); String name = null ;System.out.println("test" .equals(name)); }
铁律:比较字符串内容永远用 .equals(),别用 ==
常用方法速查
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Test public void testCommonMethods () {String s = "Hello, World!" ;System.out.println(s.length()); System.out.println(s.charAt(0 )); System.out.println(s.charAt(7 )); System.out.println(s.substring(7 )); System.out.println(s.substring(0 , 5 )); System.out.println(s.indexOf("World" )); System.out.println(s.indexOf("Java" )); System.out.println(s.contains("World" )); System.out.println(s.replace("World" , "Java" )); String csv = "苹果,香蕉,橘子" ;String[] fruits = csv.split("," ); System.out.println(fruits.length); System.out.println(fruits[1 ]); String messy = " hello " ;System.out.println(messy.trim()); System.out.println(s.toUpperCase()); System.out.println(s.toLowerCase()); System.out.println(s.startsWith("Hello" )); System.out.println(s.endsWith("!" )); System.out.println("" .isEmpty()); System.out.println(" " .isEmpty()); System.out.println(" " .isBlank()); }
方法
作用
示例
结果
length()
字符串长度
"abc".length()
3
charAt(i)
取第i个字符
"abc".charAt(1)
‘b’
substring(a,b)
截取[a,b)
"hello".substring(1,3)
“el”
indexOf(s)
查找位置
"hello".indexOf("ll")
2
contains(s)
是否包含
"hello".contains("ell")
true
replace(a,b)
替换
"abc".replace("b","x")
“axc”
split(s)
按分隔符拆分
"a,b,c".split(",")
[“a”,”b”,”c”]
trim()
去首尾空格
" hi ".trim()
“hi”
toUpperCase()
转大写
"abc".toUpperCase()
“ABC”
toLowerCase()
转小写
"ABC".toLowerCase()
“abc”
字符串拼接的性能问题
循环里用 + 拼接字符串就像每次搬家都买新房子 ,老房子直接扔了,太浪费了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void testConcatPerformance () {String bad = "" ;long start1 = System.currentTimeMillis();for (int i = 0 ; i < 10000 ; i++) { bad = bad + i; } long time1 = System.currentTimeMillis() - start1;System.out.println("+ 拼接耗时:" + time1 + "ms" ); StringBuilder good = new StringBuilder ();long start2 = System.currentTimeMillis();for (int i = 0 ; i < 10000 ; i++) { good.append(i); } String result = good.toString();long time2 = System.currentTimeMillis() - start2;System.out.println("StringBuilder 耗时:" + time2 + "ms" ); }
原理 :"a" + "b" + "c" 编译器会优化成 StringBuilder,但循环中的 + 优化不了 ,每次循环都new一个新的StringBuilder
详见 StringBuilder与StringBuffer
⭐ 安全角度:输入验证基础
用户输入的东西永远不可信 ,就像门口的快递必须先验货再签收
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 @Test public void testInputValidation () {String username = "admin" ;if (username.length() < 3 || username.length() > 20 ) { System.out.println("用户名长度必须3~20个字符" ); } String input = "<script>alert('hack')</script>" ;String safe = input.replace("<" , "<" ).replace(">" , ">" );System.out.println("过滤后:" + safe); String userInput = "' OR 1=1 --" ;String badSQL = "SELECT * FROM users WHERE name = '" + userInput + "'" ;System.out.println("危险SQL:" + badSQL); }
三条基本原则
检查长度:设置合理的最大长度
过滤特殊字符:< > ' " & 等
参数化查询:永远别把用户输入直接拼进SQL
关于字符编码的更多知识,参考 进制与编码
常见坑总结
坑
错误写法
正确写法
原因
字符串比较
s1 == s2
s1.equals(s2)
== 比地址不比内容
空指针
name.equals("test")
"test".equals(name)
name可能是null
循环拼接
s = s + i
StringBuilder.append(i)
+ 每次创建新对象
substring越界
s.substring(0, 100)
先检查 s.length()
下标超出抛异常
split正则
"1.2.3".split(".")
"1.2.3".split("\\.")
. 在正则里是通配符
练习题
题1:反转字符串
给定一个字符串,返回它的反转结果(不用StringBuilder.reverse)
1 2 3 4 5 6 7 8 9 10 @Test public void testReverse () {String input = "hello" ;StringBuilder sb = new StringBuilder ();for (int i = input.length() - 1 ; i >= 0 ; i--) { sb.append(input.charAt(i)); } System.out.println(sb.toString()); }
题2:统计字符出现次数
统计字符串中每个字符出现的次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void testCharCount () {String s = "aabbccabc" ;int [] count = new int [128 ]; for (int i = 0 ; i < s.length(); i++) { count[s.charAt(i)]++; } for (int i = 0 ; i < 128 ; i++) { if (count[i] > 0 ) { System.out.println((char ) i + " 出现了 " + count[i] + " 次" ); } } }
题3:判断回文字符串
正着读和倒着读一样的就是回文,比如 “abcba”
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testPalindrome () {String s = "abcba" ;boolean isPalindrome = true ;for (int i = 0 ; i < s.length() / 2 ; i++) { if (s.charAt(i) != s.charAt(s.length() - 1 - i)) { isPalindrome = false ; break ; } } System.out.println(s + " 是回文?" + isPalindrome); }