漫谈Java字符串

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") 这样肯定是乱码的

原文地址:https://www.cnblogs.com/zemliu/p/2764165.html