String, StringBuilder, StringBuffer
通过一些例子说明String的运行机制,首先明确确定的字符串值会提前被放入
1.最简单的常量赋值
public void test() { String test = "test"; }
查看JVM字节码
public void test(); Code: 0: ldc #2; //String test 2: astore_1 3: return
解释:
0: 从常量池获取常量#2(即"test")并将其压入栈顶
2: 将栈顶字符串引用弹出并赋给本地变量 1 (即变量test)
3: 返回
2.使用new String()构造字符串
public void test2() { String test = new String("test"); }
字节码
public void test2(); Code: 0: new #3; //class java/lang/String 3: dup 4: ldc #2; //String test 6: invokespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V 9: astore_1 10: return
解释:
0: 新建一个String对象(分配内存空间)
3: 将该内存空间的引用压入栈顶
4: 将字符串常量压入栈顶
6: 调用String的构造方法,此构造方法会用到dup压入栈中的内存空间引用
9: 将字符串常量引用弹栈并赋给变量1
10: 返回
3. 简单赋值并new一个值一样的String
public void test3() { String test = "test"; String test2 = new String("test"); }
字节码
public void test3(); Code: 0: ldc #2; //String test 2: astore_1 3: new #3; //class java/lang/String 6: dup 7: ldc #2; //String test 9: invokespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V 12: astore_2 13: return
解释:
仅看 3~12, 需要注意test2 = new String("test")与test = "test" 使用的是同一个常量池字符串
4.简单赋值并new一个值不一样的String
public void test4() { String test = "test"; String test2 = new String("test2"); }
字节码:
public void test4(); Code: 0: ldc #2; //String test 2: astore_1 3: new #3; //class java/lang/String 6: dup 7: ldc #5; //String test2 9: invokespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V 12: astore_2 13: return
解释:
和 test3() 唯一不同的地方在于第二个new String("test2")使用了新的字符串常量test2
5.字符串拼接
public void test5() { String test = "test" + "abc"; String test2 = "test"; test2 = test2 + "abc"; // 此处使用 test2 += "abc" 字节码是一样的 }
字节码:
public void test5(); Code: 0: ldc #9; //String testabc 2: astore_1 3: ldc #5; //String test 5: astore_2 6: new #10; //class java/lang/StringBuilder 9: dup 10: invokespecial #11; //Method java/lang/StringBuilder."<init>":()V 13: aload_2 14: invokevirtual #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 17: ldc #13; //String abc 19: invokevirtual #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: invokevirtual #14; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 25: astore_2 26: return
解释:
关键点在于理解
1) 使用 test = "test" + "abc"; // 编译器会预先优化,将"test"+"abc"拼接成"testabc"并放入常量池
2) 使用 test2 = test2 + "abc"; // 使用变量来拼接,将会造成"abc"作为常量先放入常量池,然后使用StringBuilder来拼接test2与"abc",最后使用toString()赋值给test2,而且虽然test2的值等于test的值,但是他们使用的却不是同一个常量池字符串.使用 test == test2 将为false
其他一些要说的:
String类的任何方法均不会改变源String,例如 Stirng.toUpperCase() String.toLowerCase()等等
StringBuilder可以用来构造String,在对字符串进行操作时,他并不新建字符串,而是对源字符串进行操作,因此效率会比String高。尤其是在循环里对字符串进行操作时,应该使用StrinBuilder。
StringBuffer与StringBuilder功能相同,只不过StringBuffer是同步类,也就是线程安全的
特别注意即使是使用StringBuilder, 在使用重载操作符时,依然会生成临时的StringBuilder对象,例如 StringBuilder.append("Abc" + "efg"); 这里的+号就会生成新的StringBuilder,也就是说+,+=用多了,会产生很多的临时StringBuilder垃圾,最后被垃圾回收机制回收
正则: Pattern, Matcher
Java的正则语法比较奇怪,和PECL有点差别,主要在于
1. 转移符号 \ 都要变成 \\, 例如 \w -> \\w , \d -> \\d, 如果要表示字符的反斜杠不转义, 则要用 \\\\
2. \t \n \r 等空白字符不需要使用 \\t \\n \\r 的形式
3. 标记语法与PECL不一样,例如 "(?imsx)\\w+", 这里 (?imsx)分别为:
i: 忽略大小写
m: 多行匹配,也就是说 ^$ 只匹配到当前行
s: 让 . 也可以匹配换行符
x: 忽略空格符和#开头行的注释
没有PECL的u参数直接关闭贪婪匹配,只能在正则语句中用?关闭贪婪匹配
Matcher.appendReplacement()方法
这个方法可以用来配合正则处理对匹配到的group做一些处理
package test; import static java.lang.System.out; import java.util.regex.*; public class Test { public static void main(String[] args) { String s = "BBabc abcef AbC,eOfg"; Pattern p = Pattern.compile("(?sm)abc"); Matcher m = p.matcher(s); StringBuffer sb = new StringBuffer(); while (m.find()) { out.println(m.group()); m.appendReplacement(sb, m.group().toUpperCase()); } m.appendTail(sb); out.println(sb); } }
输出
abc
abc
BBABC ABCef AbC,eOfg
这段程序每次查找到abc以后,可以将从当前索引到下一次匹配的到文本(下一个group之前的文本)中的group先替换成uppercase,然后将这段文本append到sb中,最后的appendTail()可以将最后一个group之后的文本append到sb中,m.group()获取的是group0.也就是整个表达式匹配到的文本
编码问题
char的编码式使用UTF-16来存储的,所以char占用两个字节(包括中文)
对于String来说,String内部使用的也是char[]数组来存储
getBytes("编码")
getBytes("编码")的意思是使用指定编码将String编码为byte[]数组,如果不指定则是使用系统默认编码来编码
如下
String s = "我"; byte[] c1 = s.getBytes(); byte[] c2 = s.getBytes("UTF-8"); byte[] c3 = s.getBytes("GBK"); System.out.println(System.getProperty("file.encoding")); System.out.println(c1.length); System.out.println(c2.length); System.out.println(c3.length);
输出
UTF-8
3
3
2
对于 new String(byte[], "编码") 来说,是使用指定的编码来解码这个byte[]数组, 也就是说byte[]的编码是固定的,我们就要使用与这个byte[]对应的编码来解码,例如
new String(s.getBytes("GBK"), "GBK") 这样就是正确的
new String(s.getBytes("UTF-8"), "GBK") 这样肯定是乱码的