《Java程序猿面试宝典》之字符串

前不久刚看完这一章,然而这遗忘速度实在是不能忍,既然总是遗忘,那么老衲就和你磨上一磨。

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"表示非数字字符。

 

posted on 2017-04-19 10:40  一生不可自决  阅读(1600)  评论(0编辑  收藏  举报

导航