Java编程思想(十二) —— 字符串之基本方法

时间:2023-02-22 19:33:40

字符串在编程中也是经常使用的。


1)不可变

其实查看API就会发现:

public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence

多谢网友提醒,不是final不可变,我短路了,Strings are constant; their values cannot be changed after they are created。

http://blog.csdn.net/renfufei/article/details/16808775


为什么不可变,通过他人写的内容进行补充一下。

public class TestString {
static String upcase(String s){
return s.toUpperCase();
}
public static void main(String[] args) {
String s = "bbc";
String ss = upcase(s);
System.out.println(s);
}
}

这里虽然s传参给了upcase方法,但是s并没有改变,传参的时候,s会复制一份引用,但是引用所指的对象却没有移动过。


再补充一下一些特别的东西。

Java编程思想(一) —— 一切都是对象 知道,内存分配中,堆是存放对象的地方,而对内存中还有一块特殊的存储区域,而常量存储区存储的是便有字符串,所以:


String a = "a";
String b = "b";
虽然a,b看似两个不同的引用,存在栈区,但其实指向的都是堆中同样的String对象。


再看看这个:

    public static void main(String[] args) {
String s= "s";
String s2= "s";
String s3= new String("s");
String s4= new String("s");
System.out.println(s==s2);
System.out.println(s3==s2);
System.out.println(s3==s4);
}
==比较的是两个对象的地址,其实由上面的a,b指向同一对象知道,s==s2,而new String呢,不会理你堆中是不是有同样的"s"对象,没每new一次,都重新创建一个,所以s3,s4,s2是不会==的。

2)重载“+”与StringBuilder

其实,StringBuilder和StringBuffer的东西,在面试笔试题里面已经看过很多遍了,但是,但是,每次都会忘记,这属性的东西,虽然一测试可以知道效率问题,但是直接回答还是凭借记忆。前段时间在知乎看到一个在外国工作的女程序员妹纸用了英文的记忆,当时记住了,答案现在找不到。


public class Test{
        public static void main(String[] args) {
        String s = "bbc";
       
        String ss = "ddt"+s+"sdf"+3;
        System.out.println(ss);
    }
}


F:\>javac Test.java
F:\>javap -c Test
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return

public static void main(java.lang.String[]);
Code:
0: ldc #2 // String bbc
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<
init>":()V
10: ldc #5 // String ddt
12: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String sdf
21: invokevirtual #6 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: iconst_3
25: invokevirtual #8 // Method java/lang/StringBuilder.ap
pend:(I)Ljava/lang/StringBuilder;
28: invokevirtual #9 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
31: astore_2
32: getstatic #10 // Field java/lang/System.out:Ljava/
io/PrintStream;
35: aload_2
36: invokevirtual #11 // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
39: return
}

作者还真是厉害,反编译class,出来一大堆汇编,之前学过8086的看起来还是挺面熟的,代码里面发现竟然自己调用了StringBuilder。因为跟作者所说的思路,如果+是String的append反复,那么每每加上一个之后就会有新对象,最后产生很多没用的对象。


竟然默认调用了StringBuilder,那么是不是可以全部都用重载+呢?

public class Test{
   public String plus(String[] array){
        String s = null;
        for (int i = 0; i < array.length; i++) {
            s += array[i];
        }
        return s;
    }
    
    public String builder(String[] array){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < array.length; i++) {
            sb.append(array[i]);
        }
        return sb.toString();
    }
}

反编译:
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return

public java.lang.String plus(java.lang.String[]);
Code:
0: aconst_null
1: astore_2
2: iconst_0
3: istore_3
4: iload_3
5: aload_1
6: arraylength
7: if_icmpge 37
10: new #2 // class java/lang/StringBuilder
13: dup
14: invokespecial #3 // Method java/lang/StringBuilder."<
init>":()V
17: aload_2
18: invokevirtual #4 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: aload_1
22: iload_3
23: aaload
24: invokevirtual #4 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: invokevirtual #5 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
30: astore_2
31: iinc 3, 1
34: goto 4
37: aload_2
38: areturn

public java.lang.String builder(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<
init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: iload_3
11: aload_1
12: arraylength
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: aaload
20: invokevirtual #4 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: goto 10
30: aload_2
31: invokevirtual #5 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
34: areturn
}

有没有发现,有默认的空参数的构造器Test();

plus方法,34 goto 4,4是i的初始化,然后再接下去运行,比较,不停地跳转,StringBuilder在循环中不停的构建。这样就有很多的StringBuilder对象浪费了。注意10那里,new的创建。


在看看,反汇编之后的build方法,循环中是没有new东西的,就只有一个StringBuilder对象。


这样分析之后,明显StringBuider在不停地对字符串进行操作是效率更高资源占用少的,如果只是简单的几个字符串合并,那可以用+。


这个例子用了反汇编之后看起来简单好多,其实有些比较是通过时间计算,当然也是可以的。


StringBuilder是在SE5版本引入,之前用的是StringBuffer,这笔试题经常有的东西,出现了,StringBuffer是线程安全的,这样开销就比StringBuilder大,所以不存在线程问题的时候,使用StringBuilder。

可以看看下面那个网址,这是知乎上的一个叫程序猎人写的(由化学读软件,这是一个很牛逼的选择)。里面就是StringBuffer VS StringBuilder。

https://gist.github.com/programus/1978494


这一看发现GitHub原来还有Gist这样好用的东西。类似于保存零散代码的仓库。

Java编程思想(十二) —— 字符串之基本方法


3)常用方法

String实现三个接口。

CharSequence:

charAt(int i), 返回字符串中该索引指向的字符。

length,字符串长度返回。

subSequence(int start, int end) ,返回索引从start开始end结束的字符串。

toString().


Comparable:

compareTo(T o) ,x.compareTo(y),这样比较。



其他常用的方法自己查JDK文档吧,常用的也用得滚瓜烂熟了。接下来会介绍字符串的格式化输出和正则表达式。