? Java之美[从菜鸟到高手演变]之字符串 亚博足球娱乐场注册,亚博娱乐全天彩,亚博官网赢钱不给
VB.net 2010 视频亚博足球娱乐场注册--任意三数字加yabo.com直达官网 VB.net 2010 视频亚博足球娱乐场注册--任意三数字加yabo.com直达官网 VB.net 2010 视频亚博足球娱乐场注册--任意三数字加yabo.com直达官网
SQL Server 2008 视频亚博足球娱乐场注册--任意三数字加yabo.com直达官网 c#入门经典亚博足球娱乐场注册--任意三数字加yabo.com直达官网 Visual Basic从门到精通视频亚博足球娱乐场注册--任意三数字加yabo.com直达官网
当前位置:
首页 > 网站开发 > JSP >
  • Java之美[从菜鸟到高手演变]之字符串

  • 2016-07-07 22:29 来源:未知
Java之美[从菜鸟到高手演变]之字符串?
写程序就像生活,有酸甜苦辣,关键在于过程,任何事情的过程都是美好的,是值得我们回味的!有人说,编程是一种艺术,艺术出于生活却高于生活,每一个细节都值得细细品味...程序员无非就是两件事:学习和分享!独乐乐与人乐乐,孰乐?于是,程序员对着电脑,开始写作。一盏台灯、一杯清茶,躺在旁边,默默的,听着。从本次博文起,要进行Java之美[从菜鸟到高手演变]系列,本文系第一篇,Java字符串的处理。字符串在任何语言中都是一个非常重要的概念,我们有必要掌握她的一切!
本博客永久更新,如有转载,
请说明出处:http://blog.csdn.net/zhangerqing
如有问题,请联系本人: egg
邮箱:xtfggef@gmail.com
微博:http://weibo.com/xtfggef
Java中的字符串处理主要有下面三个类来处理的:String、StringBuffer、StringBuilder。
一、String
1、String简介
初始化:
一般由String声明的字符串,长度是不可变的,这也是它与StringBuffer和StringBuilder最直观的一个区别。一般初始化方式:String s = "hello world";经过这条语句,JVM的栈内存中产生一个s变量,堆内存中产生hello world字符串对象。s指向了hello world的地址。像上面这种方式产生的字符串属于直接量字符串对象,JVM在处理这类字符串的时候,会进行缓存,产生时放入字符串池,当程序需要再次使用的时候,无需重新创建一个新的字符串,而是直接指向已存在的字符串。看下面程序:
[java] view plaincopyprint?
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 因为s和s2都指向了hello world字符串,他们的地址是同一个。 我们常说,String的一个很大的特点,就是它是一个“不可变的字符串”,就是说,当一个String对象完成创建后,该对象的内容就固定下来了,但是为什么还会有下面这种情况呢?
[java] view plaincopyprint?
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类的基本初始化方法。
2、String类的一些常用方法
字符串是最常用的对象,所以,我们有必要彻底的了解下它,下面我会列举常用的字符串里的方法,因为有很多,就不一一列举。
-------public int length()--------
该方法用于获取字符串的长度,实现如下:
[java] view plaincopyprint?
1. /**?
2. ? ? * Returns the length of this string.?
3. ? ? * The length is equal to the number of Unicode?
4. ? ? * code units 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种的原始实现,count在String类里被定义为一个整型常量:private final int count;并且不论采用哪种构造方法,最终都会为count赋值。
使用方法:
[java] view plaincopyprint?
1. String s = "hello world"; ?
2. int length = s.length(); ?
这个比较简单。
-----------public boolean equals(Object anObject)-----------
该方法用于比较给定对象是否与String相等。
JDK里是这样实现的:
[java] view plaincopyprint?
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. ? ?} ?
从1和2处也看出来,String的底层是基于字符数组的。我们可以像下面这种方式使用equals():
[java] view plaincopyprint?
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] view plaincopyprint?
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堆内存中的内容,==比较的是地址,也就是栈内存中的内容。
如上述代码中,s、s1、s2、s4他们四个String对象的内容都是"hello world",所以,用equals()比较他们,返回的都是true。但是,当s和s1用==比较时,却返回false,因为二者在堆中开辟的地址不一样,所以,返回的肯定是false。而为什么s和s4用==比较时,返回的是true呢,因为上文中提到过,直接量的字符串会产生缓存池,所以,当声明s4的时候,编译器检测到缓存池中存在相同的字符串,所以就直接使用,只要将s4指向s所指向的字符串就行了,二者指向同一字符串,所以地址当然相等!
注意:此处隐藏着一个比较细的编程习惯,尤其是用==进行比较的时候,尽量将常量放在==的左边,因为我们有的时候,会不小心将==写成=,这样的话,如果将常量放在左边,编译器会报错,提醒你,但是,如果将变量放在左边,常量放右边,即使你写成了=,编译器默认为变量赋值了,因此也不会报错。
因为String类实现了public interface Comparable,而Comparable接口里有唯一的方法:public int compareTo(T o)。所以,String类还有另一个字符串比较方法:compareTo()
-----------------public int compareTo(String anotherString)---------------
compareTo()可实现比较两个字符串的大小,源码如下:
[java] view plaincopyprint?
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] view plaincopyprint?
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
s和s3相比,前两个相同,不用管,直接用第三个字符的ASCII码做差就行了。所以'l'-'a'=7
此处网友“handsomeman_wei”问我源码中的c1-c2理解不了,就是上面红字部分的解释。
s2和s3相比,同第一种情况一样,只是length1比length2小,因此值为负数。
-----------public char charAt(int index)-----------
获取指定位置的字符,比较容易理解,源码为:
[java] view plaincopyprint?
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, int fromIndex)
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex)
有兴趣的自己去试试,这儿就不多阐述了。
-----------substring()------------
[java] view plaincopyprint?
1. public String substring(int beginIndex) { ?
2. ? ?return substring(beginIndex, count); ?
3. ? ?} ?
用于截取字符串,此处与另一个方法对比:
[java] view plaincopyprint?
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] view plaincopyprint?
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(2, 7); ?
7. ? ? ? ?String s3 = (String) s.subSequence(2, 7); ?
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] view plaincopyprint?
1. public CharSequence subSequence(int beginIndex, int endIndex) { ?
2. ? ? ? return this.substring(beginIndex, endIndex); ?
3. ? } ?
4. ? } ?
其实subSequence()内部就是调用的substring(beginIndex, endIndex),只是返回值不同。
subString返回的是String,subSequence返回的是实现了CharSequence接口的类,也就是说使用subSequence得到的结果,只能使用CharSequence接口中的方法。不过在String类中已经重写了subSequence,调用subSequence方法,可以直接转为String对象,如我们例子中的做法。
-----------------public String replace(char oldChar, char newChar)和public String replaceAll(String regex, String replacement)-------------------
[java] view plaincopyprint?
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] view plaincopyprint?
1. public String replaceAll(String regex, String replacement) { ?
2. ? ?return Pattern.compile(regex).matcher(this).replaceAll(replacement); ?
3. ? ?} ?
前者参数为两个字符串,用newChar替换原串里的所有oldChar。
后者从第一个参数可以看出,需要替换的东西可以用正则表达式描述。例子如下:
[java] view plaincopyprint?
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(String regex)-----------
该方法用于分割字符串,得到一个String类型的数组,根据regex可知,参数是个正则表达式。请看下面的例子:
[java] view plaincopyprint?
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
11. ? ? ? ? ? ?System.out.print(s2[i]+" "); ?
12. ? ? ? ?} ?
13. ? ? ? ?System.out.println(); ?
14. ? ? ? ?for(int j=0; j
15. ? ? ? ? ? ?System.out.print(s3[j]+" "); ?
16. ? ? ? ?} ?
17. ? ?} ?
18. } ?
输出:
hello world?
hello worldd
关于spilt()的其他重载方法,可参见JDK的String类的实现。
spilt()需要注意的事项,就是当分隔符为 . 的话,处理起来不一样,必须写成\\.因为.是正则表达式里的一个特殊符号,必须进行转义
--------------------public native String intern();--------------------(补充知识点:经网友java2000_wl提醒,特此补充,欢迎广大读者及时提出建议,我必将虚心接受!)
intern()方法和前面说的equals()方法关系密切,从public native String intern()看出,它是Java的本地方法,我们先来看看Java文档里的描述:
[java] view plaincopyprint?
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. ?
?
意思就是说,返回字符串一个规范的表示。进一步解释:有两个字符串s和t,s.equals(t),则s.intern()==t.intern().举个例子:
[java] view plaincopyprint?
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这个串,如果没有,则新增,同时返回引用,如果有,则返回已经存在的引用,此处s1和s2都指向常量池中的abc对象,所以此处是存在的,调用s.intern()后,s3和s1、s2指向同一个对象,所以s1==s3返回的是true。
intern()做到了一个很不寻常的行为:在运行期动态的在方法区创建对象,一般只有像new关键字可以在运行期在堆上面创建对象,所以此处比较特殊。属于及时编译的概念。
一般常见的字符串处理函数就这些,其它的还有很多,就不一一列举。
3、一些常见的问题,处理结果
在我们日常的开发中,总会遇到一些问题,在此我总结一下:
String s = "123" + "456"内存中产生几个字符串对象?
这是个比较有争议的问题,面试的时候,老师还挺喜欢问,论坛上大家说几个的也有,我给大家分析一下,因为我们前面有提到Java字符串的缓存机制,编译器在编译的时候会进行优化,所以在编译的过程中123和456被合成了一个字符串"123456",因此,如果缓存池中目前没有123456这个对象,那么会产生一个,即""123456",且栈中产生一个引用s指向它,如果缓存池中已经存在"123456",那么将产生0个对象,直接用s指向它。
如果spilt()函数的参数在要分割的字符串中没有怎么办?如String s = "helloworld" ,我现在调用String[] s2 = s.spilt("abc"),返回什么?
这个问题是我曾经参加红帽软件面试的时候遇到的相关题,当时懵了,像这样的题目,如果不亲自遇到过,或者看过源代码,很难准确的写出来。
做一个简单的测试,就可以看得出来:
[java] view plaincopyprint?
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] view plaincopyprint?
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,然后5和9自然连接,这里涉及到java的自动类型转换,此处int型的直接转成String类型的。第二个依次连接,都转化为String类型的了。
补充(细节):看下面的程序:
[java] view plaincopyprint?
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] view plaincopyprint?
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 = new StringBuilder(s1).append("b").toString();所以是在堆上来分配的
此处网友cowmich补充:调用s2.hashCode() == s3.hashCode()返回true。我解释下:
==比较的是他们的地址,s1+"b"会产生一个新的串,所以和s和s2用==比,返回false,如果用equals的话,返回肯定是true,因为equals()比较的是对象的内容(String类是这样的)。至于hashCode,是这样的:如果没有重写Object的hashCode(),那么如果对象调用equals()放回true,则这两个对象调用hashCode()后返回的整数一定相等。此处继续补充:对于Object类而言,原生的equals()方法,必须两个对象的地址和内容都一样才返回true,同时Object类原生的hashCode()是参照对象的地址和内容根据一定的算法生产的。所以原生的hashCode()只有调用equals()返回true才相等。而String类不同,String类重写了Object的equals(),放松了条件,只要对象地址或者内容相等就返回true,我们看看源码:
[java] view plaincopyprint?
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返回的整数值也相等,所以此处:s3和s2虽然地址不等,但是内容相等,所以会有:s2.hashCode() == s3.hashCode()返回true。但是这句话反过来讲就不一定成立了,因为毕竟hashCode()只是一种算法。继续补充:刚刚说了Object类和String类,此处补充下Integer类:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer a=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(),此处不再赘述!
个人的力量是有限的,欢迎大家积极补充,同时也欢迎读者随时批评指正!
?
相关亚博足球娱乐场注册--任意三数字加yabo.com直达官网