02_初级_Java之美[从菜鸟到高手演变]之字符串的处理

来源:互联网 发布:网络乞丐 编辑:程序博客网 时间:2024/06/07 21:49

写程序就像生活,有酸甜苦辣,关键在于过程,任何事情的过程都是美好的,是值得我们回味的!有人说,编程是一种艺术,艺术出于生活却高于生活,每一个细节都值得细细品味...程序员无非就是两件事:学习和分享!独乐乐与人乐乐,孰乐?于是,程序员对着电脑,开始写作。一盏台灯、一杯清茶,躺在旁边,默默的,听着。从本次博文起,进行Java之美[从菜鸟到高手演变]系列,本文系第一篇,Java字符串的处理。字符串在任何语言中都是一个非常重要的概念,我们有必要掌握她的一切!

本博客永久更新,如有转载,

请说明出处:http://blog.csdn.net/zhangerqing

如有问题,请联系本人: egg

邮箱:xtfggef@gmail.com

微博:http://weibo.com/xtfggef

Java中的字符串处理主要有下面三个类来处理的:StringStringBufferStringBuilder

一、String

1String简介

初始化:

一般由String声明的字符串,长度是不可变的,这也是它与StringBufferStringBuilder最直观的一个区别。一般初始化方式:String s= "hello world";经过这条语句,JVM的栈内存中产生一个s变量,堆内存中产生helloworld字符串对象。s指向了hello world的地址。像上面这种方式产生的字符串属于直接量字符串对象,JVM在处理这类字符串的时候,会进行缓存,产生时放入字符串池,当程序需要再次使用的时候,无需重新创建一个新的字符串,而是直接指向已存在的字符串。看下面程序:

[java] viewplaincopy

1.    package com.xtfggef.string;  

2.      

3.    public class StringTest4 {  

4.      

5.        public static void main(String[] args) {  

6.            String s = "hello world";  

7.            String s2 = "hello world";  

8.            System.out.println(s == s2);  

9.        }  

10.  }  

 

该程序输出:true因为ss2都指向了hello world字符串,他们的地址是同一个。我们常说,String的一个很大的特点,就是它是一个不可变的字符串,就是说,当一个String对象完成创建后,该对象的内容就固定下来了,但是为什么还会有下面这种情况呢?

[java] viewplaincopy

1.    package com.xtfggef.string;  

2.      

3.    public class StringInit {  

4.      

5.        public static void main(String[] args) {  

6.            String str = "I like";//---------1--------  

7.            System.out.println(System.identityHashCode(str));  

8.            str = str + "java";//--------2---------  

9.            System.out.println(System.identityHashCode(str));  

10.      }  

11.  }  

该程序输出:

14576877
12677476

说明:str似乎是变了,这是为什么呢?其实是这样的:str只是一个引用变量,当程序执行完1后,str指向“I like”。当程序执行完2之后,连接运算符会将两个字符串连在一起,并且让str指向新的串:"I like java",所以,从这里应该可以看得出来,最初的对象确实没有改变,只是str所指向的对象在不断改变。

String对象的另一种初始化方式,就是采用String类提供的构造方法进行初始化。String类提供了16种构造方法,常用的有五种:

String() --------- 初始化一个String对象,表示一个空字符序列

String(String value) --------- 利用一个直接量创建一个新串

String(char[] value) --------- 利用一个字符数组创建

String(char[] value,int offset,int count) --------- 截取字符数组,从offset开始count个字符创建

String(StringBuffer buffer) --------- 利用StringBuffer创建

形如:

String s = new String();

String s1 = new String(“hello”);

char[] c = {'h','e','l','l','o'};

String s2 = new String(c);

'String s3 = new String(c,1,3);

以上就是String类的基本初始化方法。

2String类的一些常用方法

字符串是最常用的对象,所以,我们有必要彻底的了解下它,下面我会列举常用的字符串里的方法,因为有很多,就不一一列举。

-------public int length()--------

该方法用于获取字符串的长度,实现如下:

[java] viewplaincopy

1.    /** 

2.         * Returns the length of this string. 

3.         * The length is equal to the number of <a href="Character.html#unicode">Unicode 

4.         * code units</a> in the string. 

5.         * 

6.         * @return  the length of the sequence of characters represented by this 

7.         *          object. 

8.         */  

9.        public int length() {  

10.          return count;  

11.      }  

这是JDK种的原始实现,countString类里被定义为一个整型常量:private final intcount;并且不论采用哪种构造方法,最终都会为count赋值。

使用方法:

[java] viewplaincopy

1.    String s = "hello world";  

2.    int length = s.length();  

这个比较简单。

-----------public boolean equals(ObjectanObject)-----------

该方法用于比较给定对象是否与String相等。

JDK里是这样实现的:

[java] viewplaincopy

1.    /** 

2.         * Compares this string to the specified object.  The result is {@code 

3.         * true} if and only if the argument is not {@code null} and is a {@code 

4.         * String} object that represents the same sequence of characters as this 

5.         * object. 

6.         * 

7.         * @param  anObject 

8.         *         The object to compare this {@code String} against 

9.         * 

10.       * @return  {@code true} if the given object represents a {@code String} 

11.       *          equivalent to this string, {@code false} otherwise 

12.       * 

13.       * @see  #compareTo(String) 

14.       * @see  #equalsIgnoreCase(String) 

15.       */  

16.      public boolean equals(Object anObject) {  

17.      if (this == anObject) {  

18.          return true;  

19.      }  

20.      if (anObject instanceof String) {  

21.          String anotherString = (String)anObject;  

22.          int n = count;  

23.          if (n == anotherString.count) {  

24.          char v1[] = value;  //---------1---------  

25.          char v2[] = anotherString.value;//-------2----------  

26.          int i = offset;  

27.          int j = anotherString.offset;  

28.          while (n-- != 0) {  

29.              if (v1[i++] != v2[j++])  

30.              return false;  

31.          }  

32.          return true;  

33.          }  

34.      }  

35.      return false;  

36.      }  

12处也看出来,String的底层是基于字符数组的。我们可以像下面这种方式使用equals()

[java] viewplaincopy

1.    String s1 = new String("hello world");  

2.    String s2 = new String("hello world");  

3.    String s3 = new String("hello");  

4.    System.out.println(s1.equals(s2));;  

5.    System.out.println(s1.equals(s3));  

结果输出:

true

false

此处插入一个很重要的知识点,重写equals()的一般步骤及注意事项:

1. 使用==操作符检查实参是否为指向对象的一个引用
2.
使用instanceof操作符检查实参是否为正确的类型 
3.
把实参转换到正确的类型。 
4.
对于该类中每一个关键域,检查实参中的域与当前对象中对应的域值是否匹配。

a.对于既不是float也不是double类型的基本类型的域,可以使用==操作符进行比较

b.对于对象引用类型的域,可以递归地调用所引用的对象的equals方法 
 c.对于float类型的域,先使用Float.floatToIntBits转换成int类型的值,然后使用==操作符比较int类型的值

d.对于double类型的域,先使用Double.doubleToLongBits转换成long类型的值,然后使用==操作符比较long类型的值。
5.
当你编写完成了equals方法之后,应该问自己三个问题:它是否是对称的、传递的、一致的?(其他两个特性通常会自行满足)   如果答案是否定的,那么请找到这些特性未能满足的原因,再修改equals方法的代码。

稍后我再做说明,请先再看个例子:

[java] viewplaincopy

1.    package com.xtfggef.string;  

2.      

3.    /** 

4.     * 字符串比较:equals()==的区别 

5.     * @author 二青 

6.     * 

7.     */  

8.    public class StringInit {  

9.      

10.      public static void main(String[] args) {  

11.            

12.          String s = "hello world";  

13.          String s1 = new String("hello world");  

14.          String s2 = new String("hello world");  

15.          String s3 = new String("hello");  

16.          String s4 = "hello world";  

17.            

18.          System.out.println(s.equals(s1));;  

19.          System.out.println(s1.equals(s2));  

20.          System.out.println(s1.equals(s3));  

21.          System.out.println("------------------");  

22.          System.out.println(s == s1);  

23.          System.out.println(s == s3);  

24.          System.out.println(s == s4);  

25.      }  

26.  }  

输出:

true
true
false
------------------
false
false
true

此处验证了一个问题,就是比较方法equals()==的区别,一句话:equals()比较的是对象的内容,也就是JVM堆内存中的内容,==比较的是地址,也就是栈内存中的内容。

如上述代码中,ss1s2s4他们四个String对象的内容都是"helloworld",所以,用equals()比较他们,返回的都是true。但是,当ss1==比较时,却返回false,因为二者在堆中开辟的地址不一样,所以,返回的肯定是false。而为什么ss4==比较时,返回的是true呢,因为上文中提到过,直接量的字符串会产生缓存池,所以,当声明s4的时候,编译器检测到缓存池中存在相同的字符串,所以就直接使用,只要将s4指向s所指向的字符串就行了,二者指向同一字符串,所以地址当然相等!

注意:此处隐藏着一个比较细的编程习惯,尤其是用==进行比较的时候,尽量将常量放在==的左边,因为我们有的时候,会不小心将==写成=,这样的话,如果将常量放在左边,编译器会报错,提醒你,但是,如果将变量放在左边,常量放右边,即使你写成了=,编译器默认为变量赋值了,因此也不会报错。

因为String类实现了public interfaceComparable<T>,而Comparable接口里有唯一的方法:public int compareTo(T o)。所以,String类还有另一个字符串比较方法:compareTo()

-----------------public intcompareTo(String anotherString)---------------

compareTo()可实现比较两个字符串的大小,源码如下:

[java] viewplaincopy

1.    public int compareTo(String anotherString) {  

2.        int len1 = count;  

3.        int len2 = anotherString.count;  

4.        int n = Math.min(len1, len2);  

5.        char v1[] = value;  

6.        char v2[] = anotherString.value;  

7.        int i = offset;  

8.        int j = anotherString.offset;  

9.      

10.      if (i == j) {  

11.          int k = i;  

12.          int lim = n + i;  

13.          while (k < lim) {  

14.          char c1 = v1[k];  

15.          char c2 = v2[k];  

16.          if (c1 != c2) {  

17.              return c1 - c2;  

18.          }  

19.          k++;  

20.          }  

21.      } else {  

22.          while (n-- != 0) {  

23.          char c1 = v1[i++];  

24.          char c2 = v2[j++];  

25.          if (c1 != c2) {  

26.              return c1 - c2;  

27.          }  

28.          }  

29.      }  

30.      return len1 - len2;  

31.      }  

compareTo是怎么实现的呢?

首先,会对两个字符串左对齐,然后从左到右一次比较,如果相同,继续,如果不同,则计算不同的两个字符的ASCII值的差,返回就行了。与后面的其他字符没关系。

举个例子:

[java] viewplaincopy

1.    package com.xtfggef.string;  

2.      

3.    /** 

4.     * compareTo()测试 

5.     * @author 二青 

6.     * 

7.     */  

8.    public class CompareToTest {  

9.      

10.      public static void main(String[] args) {  

11.          String s = "hallo";  

12.          String s2 = "ha";  

13.          String s3 = "haeeo";  

14.          int a = s.compareTo(s2);  

15.          System.out.println("a:"+a);  

16.          int b = s.compareTo(s3);  

17.          System.out.println("b:"+b);  

18.          int c = s2.compareTo(s3);  

19.          System.out.println("c:"+c);  

20.      }  

21.  }  

程序输出:

a:3
b:7
c:-3
s
s2相比,前两个相同,如果是这种情况,则直接返回length1-length2

ss3相比,前两个相同,不用管,直接用第三个字符的ASCII码做差就行了。所以'l'-'a'=7

此处网友“handsomeman_wei”问我源码中的c1-c2理解不了,就是上面红字部分的解释。

s2s3相比,同第一种情况一样,只是length1length2小,因此值为负数。

-----------public char charAt(intindex)-----------

获取指定位置的字符,比较容易理解,源码为:

[java] viewplaincopy

1.    public char charAt(int index) {  

2.            if ((index < 0) || (index >= count)) {  

3.                throw new StringIndexOutOfBoundsException(index);  

4.            }  

5.            return value[index + offset];  

6.        }  

String s = "hallo";
char a = s.charAt(2);
System.out.println(a);
输出:l

注意:参数index的值从0到字符串的长度-1,所以,如果值不在这个范围内,如下:

String s = "hallo";
char a = s.charAt(8);
System.out.println(a);

则报错:

Exception in thread "main"java.lang.StringIndexOutOfBoundsException: String index out of range: 8

at java.lang.String.charAt(String.java:686)
at com.xtfggef.string.CompareToTest.main(CompareToTest.java:20)

charAt()相对应的是indexOf():根据给定的字符串,返回他的位置。

indexOf()有多个参数:

public int indexOf(int ch)

public int indexOf(int ch, int fromIndex)

public int indexOf(String str)

public int indexOf(String str, intfromIndex)

static int indexOf(char[] source, intsourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex)

有兴趣的自己去试试,这儿就不多阐述了。

-----------substring()------------

[java] viewplaincopy

1.    public String substring(int beginIndex) {  

2.        return substring(beginIndex, count);  

3.        }  

用于截取字符串,此处与另一个方法对比:

[java] viewplaincopy

1.    public String substring(int beginIndex, int endIndex) {  

2.        if (beginIndex < 0) {  

3.            throw new StringIndexOutOfBoundsException(beginIndex);  

4.        }  

5.        if (endIndex > count) {  

6.            throw new StringIndexOutOfBoundsException(endIndex);  

7.        }  

8.        if (beginIndex > endIndex) {  

9.            throw new StringIndexOutOfBoundsException(endIndex - beginIndex);  

10.      }  

11.      return ((beginIndex == 0) && (endIndex == count)) ? this :  

12.          new String(offset + beginIndex, endIndex - beginIndex, value);  

13.      }  

前者调用后者来实现,前者截取从指定位置到字符串结束的子字符串,后者截取从指定位置开始,到endIndex-1位置的子字符串。

[java] viewplaincopy

1.    public class CompareToTest {  

2.      

3.        public static void main(String[] args) {  

4.            String s = "helloworld";  

5.            String s1 = s.substring(2);  

6.            String s2 = s.substring(27);  

7.            String s3 = (String) s.subSequence(27);  

8.            System.out.print("s1:"+s1+"\n"+"s2:"+s2+"\n"+"s3:"+s3);  

9.        }  

10.  }  

输出:

s1:lloworld
s2:llowo
s3:llowo

细心的读者应该看出来,该类里面包含一个subSequence(),而且该方法与substring(int,int)返回的结果一样,观察下源码,不难发现的区别:

[java] viewplaincopy

1.    public CharSequence subSequence(int beginIndex, int endIndex) {  

2.           return this.substring(beginIndex, endIndex);  

3.       }  

4.       }  

其实subSequence()内部就是调用的substring(beginIndex,endIndex),只是返回值不同。

subString返回的是StringsubSequence返回的是实现了CharSequence接口的类,也就是说使用subSequence得到的结果,只能使用CharSequence接口中的方法。不过在String类中已经重写了subSequence,调用subSequence方法,可以直接转为String对象,如我们例子中的做法。

-----------------public String replace(charoldChar, char newChar)public String replaceAll(String regex, Stringreplacement)-------------------

[java] viewplaincopy

1.    public String replace(char oldChar, char newChar) {  

2.        if (oldChar != newChar) {  

3.            int len = count;  

4.            int i = -1;  

5.            char[] val = value; /* avoid getfield opcode */  

6.            int off = offset;   /* avoid getfield opcode */  

7.      

8.            while (++i < len) {  

9.            if (val[off + i] == oldChar) {  

10.              break;  

11.          }  

12.          }  

13.          if (i < len) {  

14.          char buf[] = new char[len];  

15.          for (int j = 0 ; j < i ; j++) {  

16.              buf[j] = val[off+j];  

17.          }  

18.          while (i < len) {  

19.              char c = val[off + i];  

20.              buf[i] = (c == oldChar) ? newChar : c;  

21.              i++;  

22.          }  

23.          return new String(0, len, buf);  

24.          }  

25.      }  

26.      return this;  

27.      }  

[java] viewplaincopy

1.    public String replaceAll(String regex, String replacement) {  

2.        return Pattern.compile(regex).matcher(this).replaceAll(replacement);  

3.        }  

前者参数为两个字符串,用newChar替换原串里的所有oldChar

后者从第一个参数可以看出,需要替换的东西可以用正则表达式描述。例子如下:

[java] viewplaincopy

1.    package com.xtfggef.string;  

2.      

3.    public class ReplaceTest {  

4.      

5.        public static void main(String[] args) {  

6.            String s = "hello world";  

7.            String s1 = s.replace("l""d");  

8.            System.out.println(s1);  

9.              

10.          String s2 = "a78e5opx587";  

11.          String s3 = s2.replaceAll("[0-9]""");//用空串替换原串里所有的0-9的数字  

12.          System.out.println(s3);  

13.      }  

14.  }  


输出:

heddo wordd
aeopx

-------------public String[] split(Stringregex)-----------

该方法用于分割字符串,得到一个String类型的数组,根据regex可知,参数是个正则表达式。请看下面的例子:

[java] viewplaincopy

1.    package com.xtfggef.string;  

2.      

3.    public class SpiltTest {  

4.      

5.        public static void main(String[] args) {  

6.            String s = "hello world";  

7.            String s1 = "hello.worldd";  

8.            String[] s2 = s.split(" ");  

9.            String[] s3 = s1.split("\\.");  

10.          for(int i=0; i<s2.length; i++){  

11.              System.out.print(s2[i]+" ");  

12.          }  

13.          System.out.println();  

14.          for(int j=0; j<s3.length; j++){  

15.              System.out.print(s3[j]+" ");  

16.          }  

17.      }  

18.  }  

输出:

hello world 
hello worldd
关于spilt()的其他重载方法,可参见JDKString类的实现。

spilt()需要注意的事项,就是当分隔符为 .的话,处理起来不一样,必须写成\\.因为.是正则表达式里的一个特殊符号,必须进行转义

--------------------public native Stringintern();--------------------(补充知识点:经网友java2000_wl提醒,特此补充,欢迎广大读者及时提出建议,我必将虚心接受!

intern()方法和前面说的equals()方法关系密切,从public native String intern()看出,它是Java的本地方法,我们先来看看Java文档里的描述:

[java] viewplaincopy

1.    Returns a canonical representation for the string object.  

2.    A pool of strings, initially empty, is maintained privately by the  

3.    class String.When the intern method is invoked, if the pool already contains a  

4.    string equal to this String object as determined by  

5.    theequals(Object) method, then the string from the pool is  

6.    returned. Otherwise, this String object is added to the  

7.    pool and a reference to this String object is returned.  

8.    It follows that for any two strings s and t,  

9.    s.intern()==t.intern() is true if and only if s.equals(t) is true.  

10.  All literal strings and string-valued constant expressions are interned.   

11.  @return  a string that has the same contents as this string, but is  

12.  guaranteed to be from a pool of unique strings.  


意思就是说,返回字符串一个规范的表示。进一步解释:有两个字符串sts.equals(t),s.intern()==t.intern().举个例子:

[java] viewplaincopy

1.    public class StringTest {  

2.        public static void main(String[] args) {  

3.            String s = new String("abc");  

4.            String s1 = "abc";  

5.            String s2 = "abc";  

6.            String s3 = s.intern();  

7.            System.out.println(s == s1);//false  

8.            System.out.println(s == s2);//false  

9.            System.out.println(s == s3);//false  

10.          System.out.println(s1 == s3);//true        

11.      }  

12.  }  


输出结果如注释所示,前两个结果前面已经说的很清楚了,现在拿最后一个说明,首先看看s3 = s.intern()这句,当调用s.intern()这句的时候,先去字符串常量池中找,是否有abc这个串,如果没有,则新增,同时返回引用,如果有,则返回已经存在的引用,此处s1s2都指向常量池中的abc对象,所以此处是存在的,调用s.intern()后,s3s1s2指向同一个对象,所以s1==s3返回的是true

intern()做到了一个很不寻常的行为:在运行期动态的在方法区创建对象,一般只有像new关键字可以在运行期在堆上面创建对象,所以此处比较特殊。属于及时编译的概念。

一般常见的字符串处理函数就这些,其它的还有很多,就不一一列举。

3、一些常见的问题,处理结果

在我们日常的开发中,总会遇到一些问题,在此我总结一下:

String s = "123" + "456"内存中产生几个字符串对象?

这是个比较有争议的问题,面试的时候,老师还挺喜欢问,论坛上大家说几个的也有,我给大家分析一下,因为我们前面有提到Java字符串的缓存机制,编译器在编译的时候会进行优化,所以在编译的过程中123456被合成了一个字符串"123456",因此,如果缓存池中目前没有123456这个对象,那么会产生一个,即""123456",且栈中产生一个引用s指向它,如果缓存池中已经存在"123456",那么将产生0个对象,直接用s指向它。

如果spilt()函数的参数在要分割的字符串中没有怎么办?如String s ="helloworld" ,我现在调用String[] s2= s.spilt("abc"),返回什么?

这个问题是我曾经参加红帽软件面试的时候遇到的相关题,当时懵了,像这样的题目,如果不亲自遇到过,或者看过源代码,很难准确的写出来。

做一个简单的测试,就可以看得出来:

[java] viewplaincopy

1.    package com.xtfggef.string;  

2.      

3.    public class StringSpilt {  

4.        public static void main(String[] args) {  

5.            String s = "helloworld";  

6.            String[] s2 = s.split("abc");  

7.            for (int i = 0; i < s2.length; i++) {  

8.                System.out.println(s2[i] + " " + i);  

9.            }  

10.      }  

11.  }  

输出:helloworld 0

说明当遇到源字符串中没有的字符时,会把它整个串放入到数组中。spilt()的内部实现还是挺复杂的,多层嵌套,不便于放到这儿分析。

关于字符串自动类型转换分析

首先看一下题的类型:

[java] viewplaincopy

1.    int i = 2;  

2.    int j = 3;  

3.    String s = "9";  

4.    System.out.println(i+j+s);        

5.    System.out.println("-----------------------");  

6.    System.out.println(i+s+j);  

以上运算各输出什么?不妨猜猜
59
-----------------------
293

首先i+j=5,然后59自然连接,这里涉及到java的自动类型转换,此处int型的直接转成String类型的。第二个依次连接,都转化为String类型的了。

补充(细节):看下面的程序:

[java] viewplaincopy

1.    String s = "ab";  

2.    String s1 = "a";  

3.    String s2 = s1 + "b";  

4.    String s3 = "ab";   

5.    System.out.println(s == s2);//false  

6.    System.out.println(s2 == s3);//false  

7.    System.out.println(s2.hashCode() == s3.hashCode());  

8.    String s4 = "ad";  

9.    String s5 = "a" + "d";  

10.  String s6 = "ad";  

11.  System.out.println(s4 == s5);//true  

12.  System.out.println(s4 == s6);//true  



此处主要是想说明:s1+"b""a"+"b"的不同,再看一段代码:

[java] viewplaincopy

1.                    System.out.println(s1.hashCode());  

2.    System.out.println(s2.hashCode());  

3.    System.out.println(s3.hashCode());  

4.    System.out.println(s4.hashCode());  

5.    System.out.println(s5.hashCode());  

输出:

97
3105
3105
3107
3107

说明s1+"b"的过程创建了新的对象,所以地址不一样了。所以用==比较的话,返回的是false

此处继续补充:为什么s1+"b"会产生新的对象?而没有去常量池查找是否已经存在ab对象,以致于s==s2返回false。因为我们说过常量池(下文会讲常量池)是在编译期确定好的,所以如果我们的语句时String s5 = "ab"的话,这个是在编译期确定的,会去常量池查找,而此处我们的语句时s2 = s1+"b"s2的值只有在运行期才能确定,所以不会去常量池查找,也就是产生新串。再次提问:那么这里s2的值是在哪儿分配的呢?堆、JVM栈还是运行时常量池?正确回答:s2在堆上分配,因为+的内部实现是用StringBuilder来实现的。String s2 = s1+"b" 内部是这样实现的:String s2 = newStringBuilder(s1).append("b").toString();所以是在堆上来分配的

此处网友cowmich补充:调用s2.hashCode() ==s3.hashCode()返回true。我解释下:

==比较的是他们的地址,s1+"b"会产生一个新的串,所以和ss2==比,返回false,如果用equals的话,返回肯定是true,因为equals()比较的是对象的内容(String类是这样的)。至于hashCode,是这样的:如果没有重写ObjecthashCode(),那么如果对象调用equals()放回true,则这两个对象调用hashCode()后返回的整数一定相等。此处继续补充:对于Object类而言,原生的equals()方法,必须两个对象的地址和内容都一样才返回true,同时Object类原生的hashCode()是参照对象的地址和内容根据一定的算法生产的。所以原生的hashCode()只有调用equals()返回true才相等。而String类不同,String类重写了Objectequals(),放松了条件,只要对象地址或者内容相等就返回true,我们看看源码:

[java] viewplaincopy

1.    public boolean equals(Object anObject) {  

2.        if (this == anObject) {  

3.            return true;  

4.        }  

5.        if (anObject instanceof String) {  

6.            String anotherString = (String)anObject;  

7.            int n = count;  

8.            if (n == anotherString.count) {  

9.            char v1[] = value;  

10.          char v2[] = anotherString.value;  

11.          int i = offset;  

12.          int j = anotherString.offset;  

13.          while (n-- != 0) {  

14.              if (v1[i++] != v2[j++])  

15.              return false;  

16.          }  

17.          return true;  

18.          }  

19.      }  

20.      return false;  

21.      }  

同时,String类重写了hashCode()方法,只要内容相等,则调用hashCode返回的整数值也相等,所以此处:s3s2虽然地址不等,但是内容相等,所以会有:s2.hashCode() == s3.hashCode()返回true。但是这句话反过来讲就不一定成立了,因为毕竟hashCode()只是一种算法。继续补充:刚刚说了Object类和String类,此处补充下Integer类:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integera=new Integer(50),a.hashCode的值就是50。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。

补充:应网友  KingBoxing 的要求,我做下关于常量池、字符串常量池、运行时常量池的介绍:

常量池一般就是指字符串常量池,是用来做字符串缓存的一种机制,当我们在程序中写了形如String s ="abc"这样的语句后,JVM会在栈上为我们分配空间,存放变量s和对象”abc“,当我们再次需要abc对象时,如果我们写下:String s1 = "abc"的语句时,JVM会先去常量池中找,如果不存在,则新创建一个对象。如果存在,则直接将s1指向之前的对象”abc“,此时,如果我们用==来判断的话,返回的true。这样做的好处就是节省内存,系统响应的速度加快,(因为省去了对象的创建时间)这也是缓存系统存在的原因。常量池是针对在编译期间就确定下来的常量而言的,如上所说的String类的一些对象。但是,当类被加载后,常量池会被搬到方法区的运行时常量池,此时就不再是静态的了,那么是不是就不能向常量池中添加新的内容了呢(因为我们刚刚说过,常量池是在编译期确定好的)?答案是否定的,我们依然可以在运行时向常量池添加内容!这就是我们说过的String类有个方法叫intern(),它可以在运行时将新的常量放于常量池。因为我在上文中已经详细介绍过intern(),此处不再赘述!

个人的力量是有限的,欢迎大家积极补充,同时也欢迎读者随时批评指正!

有问题请联系:egg

邮箱:xtfggef@gmail.com    微博:http://weibo.com/xtfggef

You have tobelieve in yourself.That's the secretof success!

二、StringBufferStringBuilder

  1、初始化

StringBufferStringBuilder就是所谓的可变字符串类,共四个构造方法:

StringBuffer()

public StringBuffer(int paramInt)

public StringBuffer(String paramString)

public StringBuffer(CharSequence paramCharSequence)

观察其源码发现,使用StringBuffer()时,默认开辟16个字符的长度的空间,使用public StringBuffer(int paramInt)时开辟指定大小的空间,使用public StringBuffer(StringparamString)时,开辟paramString.length+16大小的空间。都是调用父类的构造器super()来开辟内存。这方面StringBufferStringBuilder都一样,且都实现AbstractStringBuilder类。

  2、主要方法

二者几乎没什么区别,基本都是在调用父类的各个方法,一个重要的区别就是StringBuffer是线程安全的,内部的大多数方法前面都有关键字synchronized,这样就会有一定的性能消耗,StringBuilder是非线程安全的,所以效率要高些。

[java] viewplaincopy

1.    public static void main(String[] args) throws Exception {  

2.            String string = "0";  

3.            int n = 10000;  

4.            long begin = System.currentTimeMillis();  

5.            for (int i = 1; i < n; i++) {  

6.                string += i;  

7.            }  

8.            long end = System.currentTimeMillis();  

9.            long between = end - begin;  

10.          System.out.println("使用String类耗时:" + between+"ms");  

11.    

12.          int n1 = 10000;  

13.          StringBuffer sb = new StringBuffer("0");  

14.          long begin1 = System.currentTimeMillis();  

15.          for (int j = 1; j < n1; j++) {  

16.              sb.append(j);  

17.          }  

18.          long end1 = System.currentTimeMillis();  

19.          long between1 = end1 - begin1;  

20.          System.out.println("使用StringBuffer类耗时:" + between1+"ms");  

21.    

22.          int n2 = 10000;  

23.          StringBuilder sb2 = new StringBuilder("0");  

24.          long begin2 = System.currentTimeMillis();  

25.          for (int k = 1; k < n2; k++) {  

26.              sb2.append(k);  

27.          }  

28.          long end2 = System.currentTimeMillis();  

29.          long between2 = end2 - begin2;  

30.          System.out.println("使用StringBuilder类耗时:" + between2+"ms");  

31.      }  

输出:

使用String类耗时:982ms
使用StringBuffer类耗时:2ms
使用StringBuilder类耗时:1ms

虽然这个数字每次执行都不一样,而且每个机子的情况也不一样,但是有几点是确定的,String类消耗的明显比另外两个多得多。还有一点就是,StringBuffer要比StringBuilder消耗的多,尽管相差不明显。

接下来介绍一些常用的方法。

-----------------------public synchronizedint length()--------------------------

-------------------------publicsynchronized int capacity()---------------------------

二者都是获取字符串的长度,length()获取的是当前字符串的长度,capacity()获取的是当前缓冲区的大小。举个简单的例子:

[java] viewplaincopy

1.    StringBuffer sb = new StringBuffer();  

2.            System.out.println(sb.length());;  

3.            System.out.println(sb.capacity());  

输出:

0

16

[java] viewplaincopy

1.    StringBuffer sb = new StringBuffer("hello");  

2.            System.out.println(sb.length());;  

3.            System.out.println(sb.capacity());  

输出:

5

21

因为默认分配16个字符大小的空间,所以不难解释上面的结果。

------------------public booleanequals(Object paramObject)---------------------

[java] viewplaincopy

1.    StringBuffer sb = new StringBuffer("hello");  

2.            StringBuffer sb2 = new StringBuffer("hello");  

3.            System.out.println(sb.equals(sb2));  

以上程序输出false,是不是有点惊讶?记得之前我们的文章说过,equals()比较的是字符串的内容,按理说此处应该输出的是true才对。

究其原因,String类重写了Objectequals(),所以只需要看内容是否相等即可,但是StringBuffer没有重写equals(),此处的equals()仍然是调用的Object类的,所以,调用StringBuffer类的equals(),只有地址和内容都相等的字符串,结果才会返回true

另外StringBuffer有一系列追加、插入、删除字符串的方法,首先append(),就是在原来的字符串后面直接追加一个新的串,和String类相比有明显的好处:

String类在追加的时候,源字符串不变(这就是为什么说String是不可变的字符串类型),和新串连接后,重新开辟一个内存。这样就会造成每次连接一个新串后,都会让之前的串报废,因此也造成了不可避免的内存泄露。

[java] view plaincopy

1.                 //append()  

2.    StringBuffer sb = new StringBuffer("helloworld, ");  

3.    sb.append("I'm ").append("erqing ").append("who ").append("are you ?");  

4.    System.out.println(sb);  

5.    //public synchronized StringBuffer insert(int paramInt, Object paramObject)  

6.    sb.insert(12/*9*/"nice! ");  

7.    System.out.println(sb);  

8.    //public synchronized StringBuffer reverse()  

9.    sb.reverse();  

10.  System.out.println(sb);  

11.  sb.reverse();  

12.  System.out.println(sb);  

13.  //public synchronized StringBuffer delete(int paramInt1, int paramInt2)  

14.  //public synchronized StringBuffer deleteCharAt(int paramInt)  

15.  sb.delete(1218);  

16.  System.out.println(sb);  

17.  sb.deleteCharAt(5);  

18.  System.out.println(sb);  

输出:

helloworld, I'm erqing who are you ?
helloworld, nice! I'm erqing who are you ?
? uoy era ohw gniqre m'I !ecin ,dlrowolleh
helloworld, nice! I'm erqing who are you ?
helloworld, I'm erqing who are you ?
helloorld, I'm erqing who are you ?

-----------------public synchronized voidtrimToSize()---------------------

该方法用于将多余的缓冲区空间释放出来。

[java] viewplaincopy

1.                  StringBuffer sb = new StringBuffer("hello erqing");  

2.    System.out.println("length:"+sb.length());  

3.    System.out.println("capacity:"+sb.capacity());  

4.    sb.trimToSize();  

5.    System.out.println("trimTosize:"+sb.capacity());  

输出:

length:12
capacity:28
trimTosize:12

StringBuffer类还有很多方法,关于字符查找,截取,替换方面的方法,有兴趣的童鞋可以去研究研究源码,定会学到不少知识!

三、字符串处理类StringTokenizer

StringTokenizerjava.util包下的一个类,用来对字符串做简单的处理。

举个简单的例子:

[java] viewplaincopy

1.    String s = "Tonight is the answer !";  

2.            StringTokenizer st = new StringTokenizer(s," ");  

3.            int count = st.countTokens();  

4.            System.out.println("个数为:"+count);  

5.            while (st.hasMoreTokens()) {  

6.                String token = st.nextToken();  

7.                System.out.println(token);  

8.            }  

输出:

个数为:5
Tonight
is
the
answer
!

本章节完!后续会不断的增加。

本内容随时更新,如果大家有什么新的内容需要补充,希望留言!

 

 

分享到: 

· 上一篇:【微软面试题】找出任何相邻子向量的最大和

· 下一篇:HDOJ Problem 1000 Java实现

15

0

查看评论

17 tcg670566997 昨天 17:56发表 [回复]

为什么字符串比较相等老是用==而不是.equal()呢!在实际编程当中也是用的==吗?

Re: ErinToJerry 昨天 09:56发表 [回复]

回复tcg670566997:感觉用equals更多一些吧!……

Re: 终点 昨天 19:29发表 [回复]

回复ErinToJerry:当你知道原理之后,用什么自己清楚就行了,如果别人在某个地方用的不对,你可以指正!

Re: 终点 昨天 09:45发表 [回复]

回复tcg670566997:谁说的实际编程中用==呢?我怎么一般都用equals呢?估计你是那样的情况吧,而且很多时候人们不太注意这个,但是不妨碍你弄懂它的原理,以后依据情况而定,这样才能不会出错!写出高质量的代码!

16 zh70118 2013-01-01 22:41发表 [回复]

你好能下载你这个资料吗?我想好好学习下。因为我的上网时间不多

Re: 终点 2013-01-01 23:18发表 [回复]

回复zh70118:目前我没有整理,你自己直接从网页上复制吧,粘贴到word里,虽然格式有点儿乱,但是基本没有影响,感谢支持!

Re: 终点 4天前 10:12发表 [回复]

回复zhangerqing:学习要深入,注重基础,我博客里讲的这10多篇文章都要看懂,深入理解,多动手练习,提高编程能力。学Java一定要清楚语言的原理。

Re: zh70118 2013-01-0123:52发表 [回复]

回复zhangerqing:前辈你好:我是一个刚学习java10天左右的菜鸟。在传智培训。你前辈指点下学习方面注意的事项,应该如何学习才是最有效和最高效呢?不甚感激

15 dengpengks 2012-12-20 14:43发表 [回复]

楼主真真是高手啊!虽然做了这么久,都不及你理解的透彻!

Re: 终点 2012-12-20 20:09发表 [回复]

回复dengpengks:呵呵,过奖了!

14 liusofttech 2012-12-14 23:35发表 [回复]

请问:
String
源码中进行compareToIgnoreCase比较时,调用了一个实现了Comparator<String>接口的静态内部类对象CASE_INSENSITIVE_ORDE进行实现,为什么不像compareTo(String anotherString)方法那样直接实现,而采用这种方式来进行实现?
而且CASE_INSENSITIVE_ORDER为什么设计成公有的?
谢谢!!

13 britain 2012-12-12 17:53发表 [回复]

弱弱的问一句:
String
StringBufferStringBuilder效率的比较的那个例子中(通过System.currentTimeMillis()获取时间进行比较的那个例子)
System.out.println(string.equals(sb));
System.out.println(string.equals(sb2));
System.out.println(sb2.equals(sb));
System.out.println(sb.equals(sb2));
为什么输出都是false,不是String重载了equals(),输出前两个应该为 true么?

Re: 终点 2012-12-12 20:28发表 [回复]

回复weiyingguo:首先给你看看Stringequals()的源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {//--------222--------
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
注意标注着222的注释的地方,它会先通过instanceof判断类型的,如果是String类型才去看内容是否相等,因为可以这样说:String类和StringBuffer根本不是同样的类,怎么比较?所以如果你改成:System.out.println(string.equals(sb.toString()));肯定就输出true了,因为sb调用toString(),会将sb对象转换成String类型的对象。toString方法源码如下: 
public synchronized String toString() {
return new String(value, 0, count);
}
明白了吧?

Re: britain 2012-12-1223:06发表 [回复]

回复zhangerqing:哦,明白了,非常感谢!!!

12 britain 2012-12-12 16:52发表 [回复]

讲解的很精辟、透彻,受教了!非常感谢大神的分享。我还处于菜鸟阶段,收藏下来,慢慢消化,谢谢!希望以后能有更多这样好的文章!

Re: 终点 2012-12-12 17:31发表 [回复]

回复weiyingguo:呵呵,过奖了!

11 KingBoxing 2012-12-07 14:32发表 [回复]

String str = "ab";
String a = "a";
String b = "b";
String str2 = a + b;

str == str2 //
输出是false为什么??

Re: 终点 2012-12-07 15:51发表 [回复]

回复aaa1117a8w5s6d:有变量参与的用”+“连接的过程会产生新的串(如a+b或者a+”b“都会产生新的串),所以用==比较,肯定返回false啦!如果你改成String str3 = "a"+"b";则str==str3返回的是true。(因为没有产生新的串)

Re: KingBoxing 2012-12-07 16:05发表 [回复]

回复zhangerqingString str4 ="aaa";
这个aaa也是产生的新串,java会去字符串池内搜有没有aaa.
但是String str2 = a + b;a+b产生的新串怎么就不去字符串池内搜有没有对应的串呢??不好意思我比较专牛角尖,还望大侠多多指点。

Re: 终点 2012-12-07 17:03发表 [回复]

回复aaa1117a8w5s6d:一般情况下,只有在编译期才会去常量池中找是否已经存在,String str4 = ”aaa“这样的对象是在编译期确定的,所以会去找。而String str2 = a + b这样的是在运行期确定的,所以不去找,这下你应该明白了吧?

Re: KingBoxing 2012-12-07 17:10发表 [回复]

回复zhangerqing:明白了,多谢大侠!

Re: KingBoxing 2012-12-07 17:25发表 [回复]

回复aaa1117a8w5s6d:还有一个问题:既然Stringstr2 = a + bstr2是在运行期确定的,那么str2是在哪里分配内存呢?运行时常量池还是JVM栈?

Re: 终点 2012-12-07 17:35发表 [回复]

回复aaa1117a8w5s6dstr2在堆上分配,因为+的内部实现是用StringBuilder来实现的。String str2 = a + b 内部是这样实现的:Stringstr2 = new StringBuilder(a).append(b).toString();所以是在堆上来分配的。

Re: KingBoxing 2012-12-07 17:47发表 [回复]

回复zhangerqing:可不可以这样理解,在运行期才能确定的对象(无论是什么对象),在内存中都被分配到堆上????

Re: 终点 2012-12-07 19:44发表 [回复]

回复aaa1117a8w5s6d:不能那么绝对,现在很流行的Java实时编程,就能满足在运行时不一定都在堆上分配内存,String类的intern()就是个例子,还有一些我不知道的。但是目前大部分都是的。

10 KingBoxing 2012-12-07 14:25发表 [回复]

你好,字符串池是存在堆区还是JVM栈区??还有我认为String str ="abc"; abc是在栈内分配内存,而Stringstr = new String("abc"); 这个abc才是在堆里分配内存!

Re: 终点 2012-12-07 15:39发表 [回复]

回复aaa1117a8w5s6d:常量池在栈中,运行时常量池在方法区。String str = "abc"; abc是在栈内分配内存(因为Java8中数据类型机上引用都是在栈上分配的。),而String str = new String("abc"); 这个abc是在堆里分配内存。你说的很对!文中我好像写的不对,感谢指正!

Re: KingBoxing 2012-12-07 15:48发表 [回复]

回复zhangerqing:麻烦大侠帮我解释一下:常量池、运行时常量池、字符串池。他们都是什么,都用来存什么的?

Re: 终点 2012-12-07 16:23发表 [回复]

回复aaa1117a8w5s6d:已在文中增加介绍:
常量池一般就是指字符串常量池,是用来做字符串缓存的一种机制,当我们在程序中写了形如String s = "abc"这样的语句后,JVM会在栈上为我们分配空间,存放变量s和对象”abc“,当我们再次需要abc对象时,如果我们写下:String s1 ="abc"的语句时,JVM会先去常量池中找,如果不存在,则新创建一个对象。如果存在,则直接将s1指向之前的对象”abc“,此时,如果我们用==来判断的话,返回的true。这样做的好处就是节省内存,系统响应的速度加快,(因为省去了对象的创建时间)这也是缓存系统存在的原因。常量池是针对在编译期间就确定下来的常量而言的,如上所说的String类的一些对象。但是,当类被加载后,常量池会被搬到方法区的运行时常量池,此时就不再是静态的了,那么是不是就不能向常量池中添加新的内容了呢(因为我们刚刚说过,常量池是在编译期确定好的)?答案是否定的,我们依然可以在运行时向常量池添加内容!这就是我们说过的String类有个方法叫intern(),它可以在运行时将新的常量放于常量池。因为我在上文中已经详细介绍过intern(),此处不再赘述!

Re: KingBoxing 2012-12-07 16:33发表 [回复]

回复zhangerqing:大侠好强啊~请问大侠这些知识都是从哪学的啊?能否推荐一些资料或书籍?

Re: 终点 2012-12-07 17:18发表 [回复]

回复aaa1117a8w5s6d:源码+实验+分析,书的话,尽量看一些很专的书,一本书只讲一个模块,肯定会分析的比较细。一般的书只讲个大概,不会讲太深的。不过话又说回来了,很多深的东西需要自己去挖掘,反复实验,才能记得牢,如果什么东西都是书上告诉我们的,那我们就只能是拿来背诵了,自己学到的东西就没有了。

Re: KingBoxing 2012-12-07 17:21发表 [回复]

回复zhangerqing:受益匪浅啊,以后还望您多多指点迷津

9 cowmich 2012-12-06 16:55发表 [回复]

关于字符串自动类型转换分析下面的补充
String s1 = "ab";
 
String s2 = s1 + "b";
 
String s3 = "ab";
 
S2
这时是s1+"b""abb"就是与S1不同
改成Strings="a";String s2=s+"b";
打印s1==s2;结果为false;
打印s1s2hashCode确是相等的

Re: 终点 2012-12-06 22:00发表 [回复]

回复cowmich==比较的是他们的地址,s+"b"会产生一个新的串,所以和s1==比,返回false,如果用equals的话,返回肯定是true。至于hashCode,是这样的:如果没有重写ObjecthashCode(),那么如果对象调用equals()放回true,则这两个对象调用hashCode()后返回的整数一定相等。这句话反过来讲就不一定成立了。

Re: cowmich 2012-12-0708:34发表 [回复]

回复zhangerqing:谢谢说明楼主很用心的在写文章

Re: 终点 2012-12-07 09:15发表 [回复]

回复cowmich:嗯,呵呵!不过这儿的回复只是针对Object的,如果没有重写Object类的hashCode()...”其实String类是重写了hashCode()的。所以更好的解释我放在原文里了。谢谢支持啊!

Re: cowmich 2012-12-0709:22发表 [回复]

回复zhangerqing:嗯嗯看到了博主之前就有篇写到了equalshashcode
是被java之美系列吸引了呵呵我会继续支持关注的 
o(∩_∩)o

8 licl19870605 2012-12-04 21:53发表 [回复]

String s = "123" + "456"内存中产生几个字符串对象?
-------------------------------
这个问题是否要考虑“123”“456”两个对象的生成以及缓存池中是否有这两个对象?

Re: 终点 2012-12-04 21:58发表 [回复]

回复licl19870605:编译器自动合并成123456,如果缓存池中有,则直接指向,如果没有,则创建新的。过程中的123456没有被创建出来。

7 java2000_wl 2012-11-19 21:52发表 [回复]

学习了 
String

public native String intern();
方法也很有用

6 womimangl 2012-11-14 09:49发表 [回复]

谢谢分享,还没有看完,改天再看了。

5 HandsomeMan_wei 2012-11-01 21:32发表 [回复]

new GridLayout(5,2,3,6);翻看了所有的资料,找不到GridLayout布局管理器还有这样的构造函数,请帮忙解释一下这四个参数,谢谢

Re: 终点 2012-11-01 21:46发表 [回复]

回复HandsomeMan_wei:这种问题,去看JDK源码,网上的资料很多都是抄来抄去。我随便看了一下,的确有这个构造函数,如下:
public GridLayout(int paramInt1, int paramInt2, int paramInt3, int paramInt4)
{
if ((paramInt1 == 0) && (paramInt2 == 0))
throw new IllegalArgumentException("rows and cols cannot both bezero");

this.rows = paramInt1;
this.cols = paramInt2;
this.hgap = paramInt3;
this.vgap = paramInt4;
}
前两个是行数和列数,后两个你再研究研究,我现在忙,回头给你看看。

Re: HandsomeMan_wei 2012-11-01 22:01发表 [回复]

谢啦,已经弄明白了,后两个是水平与竖直距离

Re: 终点 2012-11-01 22:02发表 [回复]

回复HandsomeMan_wei:嗯,呵呵,我也是很久没有弄过GUI了,多看看源码。

4 HandsomeMan_wei 2012-10-31 14:27发表 [回复]

compareTo()方法里count value以及offset 分别是什么,值等于多少?

Re: 终点 2012-10-31 20:36发表 [回复]

回复HandsomeMan_weicount是字符串里元素的个数,offset是起始位置,value是字符数组,这个你得结合着String类的源码来看。因为我那个是从源码中截取出来的,像你问的这些变量,String类里有定义,我这儿省略了。

3 终点 2012-10-3009:51发表 [回复]

引用“HandsomeMan_wei”的评论:我初学的,看了这篇文章,收获挺大的,String类的一些细节问题注意到了,还有一个小算法,就是Com...
你是说哪个小算法?

2 HandsomeMan_wei 2012-10-29 19:39发表 [回复]

我初学的,看了这篇文章,收获挺大的,String类的一些细节问题注意到了,还有一个小算法,就是CompareTo方法里的……赶紧更新哈

1 HandsomeMan_wei 2012-10-29 19:16发表 [回复]

CompareTo()方法不太懂,既然这个方法的最后 return len1-len2;那么if语句中 return c1 - c2 ;有什么用啊

Re: 终点 2012-10-30 09:54发表 [回复]

回复HandsomeMan_wei:这个有不同的情况,如果两个字符串,一个是hello,一个是hellos,想这样的直接就是len1-len2.但是如果一个是hello,一个是hellp,这样返回的值就是c1-c2了,即'o'-'p'。用二者ASCII的值作差。

 

原创粉丝点击