前不久刚看完这一章,然而这遗忘速度实在是不能忍,既然总是遗忘,那么老衲就和你磨上一磨。
1.字符串基础
先说字符串吧,看例1:
1 String a = "abc"; 2 String b = "abc"; 3 a==b; //true 4 a.equals(b) //true
再来看看例2:
String a = new String("abc"); String b = new String("abc"); a==b; //false a.equals(b); //true
在例1中,"abc"是放在常量池(Const pool)中的,所以虽然a,b都等于"abc",但是内存中只有一份副本,所以"==" 返回true。"abc"是编译期常量,编译时已经能够确定它的值,在编译好的class文件中,它已经在String Pool中了, String a = "abc"; 会在String Pool中查找等于"abc"的字符串(用equals),若存在就把引用返回,若不存在就会创建一个"abc"放在String Pool中,然后把引用返回。
在例2中,new方法决定了两个不同的String "abc"被创建放在了内存heap区,分别被a,b指向,因此,"==" 返回了false;这里要注意的一点是:Const Pool存储在Method Area中,而不是堆中。所以,我们可以看看例3:
1 String s1 = new String("aaa777"); 2 String s2 = "aaa777"; 3 System.out.println(s1==s2);//结果为false
在Java中,使用new关键字会创建一个新对象,在本例中,不管在String Pool中是否已经有值相同的对象,都会创建一个新的String对象存储在heap中,然后返回其引用。s2指向的对象存储在String Pool中,他们肯定不是同一个对象,只不过存储的字符串相同罢了,所以返回的结果必然是false。
下面我们再来延伸一下:
String s = "a" + "b" + "c" + "d" + "e";
这里一共创建了几个对象?A.没有创建 B.1个对象 C.2个对象 D.3个对象
答案是:B 要注意的是,赋值语句后面部分的"a"、"b"、"c"、"d"、"e"都是常量,对于常量,编译时就直接存储他们的字面值,而不是它们的引用,在编译时就直接将它们连接的结果提取出来变成了"abc"。关于这个String类重载的连接符,后面我们还会讲到,这里先点到为止。再说一点,String使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容,就是因为如此,才说String类型是不可变的(immutable)。
再看例4:
1 String s1 = new String("aaa777"); 2 s1 = s1.intern(); 3 String s2 = "aaa777"; 4 System.out.println(s1==s2);
你猜一猜这个最终的执行结果会是什么?是true!没错,是true!
当调用intern方法时,如果String Pool中已经包含一个等于此String对象的字符串(用equals确定),则返回池中的字符串,否则将此String对象添加到池中,并返回此String对象在String Pool中的引用。由于执行了 s1 = s1.intern(); ,会使s1指向String Pool中值为"aaa777"的字符串对象,s2也指向了同样的对象,所以结果为true.
例5:
1 String s1 = new String("777"); 2 String s2 = "aaa777"; 3 String s3 = "aaa"+"777"; 4 String s4 = "aaa" + s1; 5 System.out.println(s2==s3); //true 6 System.out.println(s2==s4); //false 7 System.out.println(s2==s4.intern()); //true
显然,行5和行7结果就不需要我来讲了,问题在行6。为什么行6的结果是false呢?由于s1是变量,在编译期不能确定它的值是多少,所以会在执行的时候创建一个新的String对象存储到heap中,然后赋值给s4!注意,是heap中,而s2在Const Pool中,显然s2和s4的引用值自然是不相等的。
好了,字符串基础快讲完了,有点长,最后我们再看一个函数结束吧~
1 String str = "ABCDEFGH"; 2 String str1 = str.substring(3,5); 3 System.out.println(str1);
猜猜结果是多少?"DEF"吗?差点对=。= 这里要注意啊,java中的substring是前包括后不包括的,所以应该是"DE"。
2.StringBuffer
例1:
1 String result = "hello" + "world"; 2 StringBuffer result = new StringBuffer.append("hello").append("world");
行1的效率好于行2,这是因为JVM会进行如下处理:
- 将result字符串进行"hello"+"world"处理,然后才赋值给result,只开辟了一次内存段。
- 编译StringBuffer后还要进行append处理,开辟了一次内存,扩展了2次,花的时间要长一些。
例2:
1 public String getString(String s1,String s2){ 2 return s1+s2; 3 } 4 5 public String getString(String s1,String s2){ 6 return new StringBuffer().append(s1).append(s2); 7 }
这两个的效率是一样的,都是先开辟一个内存段,再合并(扩展)内存,所以两者执行的过程是一致,效率相当。
例3:
(1)String s = "s1"; s+="s2"; s+="s3"; (2) StringBuffer s = new StringBuffer().append("s1").append("s2").append("s3");
(2)的效率好于(1),因为String是不可变对象,每次"+="操作都会构造新的String对象,实际上是另外创建了一个对象,而原来指向的那个对象就成了垃圾,比如如下代码:
1 String tmp = ""; 2 for(int i =0;i<9999;tmp += "x"){}
一个循环就产生了n个对象,从而造成内存和时间的浪费。
例4:
1 (1)StringBuffer s = new StringBuffer(); 2 for(int i=0;i<50000;i++){ 3 s.append("hello"); 4 } 5 (2)StringBuffer s = new StringBuffer(250000); 6 for(int i=0;i<50000;i++){ 7 s.append("hello"); 8 }
(2)的效率好于(1),因为StringBuffer内部实现的是char数组,默认初始化长度为16,每当字符串长度大于char数组长度的时候,JVM会构造更大的新数组,并将原先的数组复制到新数组,(2)避免了数组复制的开销。
最后再看一个例子:
例5,以下程序创建了几个对象?A. 4 B. 3 C. 5 D. 6
1 String A,B,C; 2 A = "a"; 3 B = "b"; 4 A = A + B; 5 StringBuffer D = new StringBuffer("abc"); 6 D = D.append("567");
首先,我们先搞清几个概念:
- 引用变量与对象。A aa; 语句声明一个类A的引用变量aa(常称为句柄),而对象一般通过new创建。所以题目中D仅仅是一个引用变量,他不是对象。而字符串"abc"是一个String对象
- Java中所有的字符串文字(字符串常量)都是一个String的对象。有人在一些场合喜欢把字符串当做字符数组,因为字符串与字符数组存在一些内在的联系。事实上,它与字符数组是两种完全不同的对象。如System.out.println("Hello".length()); 这里length()显然是对象的方法,而char[] cc = {'H','i'};System.out.println(cc.length); 这里的cc.length则是数组的属性,要注意区分。
字符串对象的创建。由于字符串对象的大量的使用,Java中为了节省内存空间和运行时间,在编译阶段就把所有的字符串文字放到一个文字池(pool of literal strings)中,而运行时文字池成为常量池的一部分。文字池的好处就是该池中所有相同的字符串常量被合并,只占用一个空间。我们来看一段代码:
1 String s1 = new String("abc"); 2 String s2 = new String("abc");
String s1 = new String("abc");语句,这里"abc"本身就是pool中的一个对象,而在运行时执行new String()时,将pool中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s持有。这条语句就创建了2个String对象。于是,上面的两行代码创建了3个对象,pool中一个,heap中2个!
OK,我们现在来对例5进行一下解析。
StringBuffer D = new StringBuffer("abc"); 产生了两个对象,"abc"本身与经过new创建出来的不是一个对象。 A=A+B; 此处创建了一个对象,并由引用A来引用,那么原来A所指向的对象就成为了垃圾,被回收。StringBuffer的特点是改变对象本身而不是创建新的对象,因此,此处 D= D.append("567"); 都是对同一个对象进行处理。所以整个例5一共创建了1+1+1+2=5个对象,答案是C
3.正则表达式
例1: String s = "32fdsfd8fds0fdsf9323k32k" ,从中找出3280932332,你会怎么做?
一般做字符串替换,我们能想到的一般方法都是正则表达式。所以可以这样: String a = s.replaceAll("[^0-9]",""); ,"[^0-9]"是正则表达式,表示除了0到9以外的字符,这条代码的意思是将s中所有非0-9的字符替换为空串。
例2: String str = "2006-04-15 02:31:04" ,要把这个串变成20060415023104,你会怎么做?
首先,这个简单的任务可以用最笨的方法:
str = str.replaceAll("-","");str = str.replaceAll(":","");str = str.replaceAll(" ",""); ,不过这有点太low逼了,看下面的方法:
1 class Test{ 2 public static void main(String[] args){ 3 String str = "2006-04-15 02:31:04"; 4 String str2 = ""; 5 String[] result = str.split("\D"); 6 for(int i=0;i<result;i++){ 7 System.out.print(result[i]); 8 str2 += result[i]; 9 } 10 System.out.println(str2); 11 } 12 }
这里"\D"表示非数字字符。