Java字符串解惑

2012-12-12  付民 

Java字符串在我们平时接口测试中使用频率较高,很多人认为这部分很基础没有什么值得探讨的。但是别小看它哦,简单的东东也蕴藏着许多小陷阱呢嘿嘿。。。

有兴趣的我们一起往下看,顺便可以测测看你是不是真正掌握了基础的Java String类呢(*^__^*) 嘻嘻……

一、认识字符串类String

很多语言中字符串被当做字符数组来处理,那java呢?

• 在Java中,字符串是一个对象,是由字符构成的一个序列
• String变量存储的是对String对象的引用,String对象里存储的才是字符串的值
• String对象是不可变的,它的内容是不可改变的
 
二、常见错误解析 

问题1:

String str1 = "Java";

String str1 = “HTML";

上面的这段代码会改变字符串的内容吗?

解析:不能,上面这样写也是错误的会直接编译错误

第一条语句创建了一个内容为“Java”的String对象,并将其引用赋值给str1.

第二条语句创建了一个内容为“HTML”的新String对象,并将其引用同样赋值给str1.

赋值后第一个String对象仍然存在但是不能访问它,因为变量str1现在指向了新的对象。

总结:字符串是不可变的,一旦创建了它们的内容是不能修改的

 

问题2:

String的不变性的机制显然会在String常量内有大量的冗余,而字符串在程序设计中又是频繁使用的。

那么java如何处理这种状况呢?

解析:Java为了更有效地使用内存,JVM留出一块特殊的内存区域,被称为“String常量池”

当编译器遇见String常量的时候,它检查该池内是否已经存在相同的String常量。如果找到,就把新常量的引用指向现有的String,不创建任何新的String常量对象。

那么就可能出现多个引用指向同一个String常量,会不会有别名的危险呢?

No problem!String对象的不变性可以保证不会出现别名问题!这是String对象与普通对象的一点区别。

乍看起来这是底层的机制,对我们编程没什么影响。而且这种机制会大幅度提高String的效率,实际上却不是这样。

为连接n个字符串使用字符串连接操作时,要消耗的时间是n的平方级!因为每两个字符串连接,它们的内容都要被复制。

 

问题3:

在处理大量的字符串连接并且对性能有要求时怎么办?

解析:

此时我们不要用String,StringBuffer是更好的选择

我们顺带介绍下面两个常用类:

• StringBuffer类
• StringBuilder类
从J2SE 5.0 提供了StringBuilder类,它和StringBuffer类是孪生兄弟,很像。
 
 • 相同点:

均是可变的,不会在字符串常量池中而是在堆中,不会留下一大堆无用的对象

比String类更灵活,可以添加、插入或追加新的内容

• 不同点:

StringBuilder类对字符串操作的效率更高。不足的是线程安全无法保证,不保证同步。

如果是多任务并发访问就使用StringBuffer

如果是单任务访问,使用StringBuilder更有效


问题4: 下面代码分别输出结果是什么呢?

StringBuilder sb = new StringBuilder();

sb.append("Hello");

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

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

解析:分别输出16和5

Capacity()方法返回字符串生成器当前的容量。——在计算机内部,字符串生成器是一个字符数组,因此,生成器的容量就是数组的大小。

Length()方法返回字符串生成器中实际存储的字符数量。

字符串的长度总是小于或等于生成器的容量。长度是存储在生成器中的字符串的实际大小,而容量是生成器的当前大小。

StringBuilder()——构建一个容量为16的空的字符串生成器

 

问题5:引申下如下这段代码capacity的输出结果又是多少呢?
StringBuilder sb = new StringBuilder();

sb.append("Hello");

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

System.out.println(sb.length());//字符串长度为5

sb.append("123456789123");//增加了长度为12的字符串

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

解析:最后一行代码capacity输出34

如果有更多的字符添加到字符串生成器,超出它的容量,则生成器的容量就会自动增加,用新的数组替换现有数组。

新数组的大小为2*(前一个数组的长度+1)。

可以使用new StringBuilder(initialCapacity)创建指定初始容量的StringBuilder。仔细选择初始容量能够使程序更有效。

如果容量总是超过生成器的实际长度,JVM将永远不需要为生成器重新分配内存。另一方面,如果容量过大,将会浪费内存空间,可以使用trimToSize()方法将容量降到实际大小。

 

引申思考:

性能方面考虑我们是不是应该更倾向于用StringBuilder呢?

如果大家对StringBuilder与StringBuffer的性能差异感兴趣可以参考http://www.blogjava.net/43880800/archive/2007/02/20/100272.html

这篇文章做了两者的实验写的非常清晰总结的也非常好。

结论:

1.  为了获得更好的性能,在构造 StirngBuffer 或 StirngBuilder 时应尽可能指定它的容量。当然,如果你操作的字符串长度不超过 16 个字符就不用了。

2.  相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非你能确定你的系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,否则还是用 StringBuffer 吧!

3.  用好现有的类比引入新的类更重要。很多程序员在使用 StringBuffer 时是不指定其容量的(至少我见到的情况是这样),如果这样的习惯带入 StringBuilder 的使用中,你将只能获得 10 %左右的性能提升(不要忘了,你可要冒多线程的风险噢);但如果你使用指定容量的 StringBuffer ,你将马上获得 45% 左右的性能提升,甚至比不使用指定容量的 StirngBuilder 都快 30% 左右。

 

问题5:我们知道java里==比较的是引用,这里结果大家猜猜看呢?

String str1 = "java";

String str2 = "java";

System.out.print(str1==str2);

解析:输出:true

地球上有点Java基础的人都知道会输出false,因为==比较的是引用,equals比较的是内容。 但是结果是true!

原因很简单,String对象被放进常量池里了,再次出现“java”字符串的时候,JVM很兴奋地把str2的引用也指向了“java”对象,它认为自己节省了内存开销。

 

亲,再来猜猜看结果 呢?如果这两个例子都用equals来比较呢?结果又是什么呢?

String str1 = new String("java");

String str2 = new String("java");

System.out.print(str1==str2);

解析:结果是false!

new方式是直接在内存堆里面创建对象,每次new都会创建一个新对象,所以str1和str2虽然值一样但是地址不同, ==返回false.

无论在常量池还是堆中的对象,用equals()方法比较的就是内容,就这么简单!

 

问题6:下面哪条语句出错了呢?

String str1 = null;

String str2 = "";

System.out.print(str1.isEmpty());

System.out.print(str2.isEmpty());

解析:第一种出错了哦

空串“”与null,它们是不同的概念!

null是一个特殊的初始化值,是一个不指向任何对象的引用,对引用为null的对象操作会在运行时抛出异常NullPointerException

空串是长度为0的字符串,和别的字符串的唯一区别就是长度为0

 

三、常用的String类的方法

 •常用String类的方法

1.字符串比较

equals()

equalsIgnoreCase()

compareTo()

compareToIgnoreCase()

说明:方法compareTo返回的实际值是依据s1和s2从左到右数第一个不同字符之间的距离得出的。

例如s1为“abc”,s2为“abd”,那么s1.compareTo(s2)返回-1.

 

2.字符串长度、字符

length()

charAt()

说明:s.charAt(index)可用于提取字符串s中的某个特定字符,其中下标index的取值范围从0-s.length-1

 

3.字符串获取

substring(beginIndex:int):String

说明:返回这个字符串中以指定的beginIndex开始并延续到这个字符串末尾的子串

substring(beginIndex:int,endIndex:int):String

说明:返回这个字符串中以指定的beginIndex开始并延续到下标为endIndex-1的字符串的子串。

注意:在endIndex处的字符不是子串的一部分。

例子:

String str1 = "abcd";

System.out.print(str1.substring(1, 3));

输出的是“bc"而不是"bcd"

 

4.字符串大小写、替换、分隔

toLowerCase();

toUpperCase();

trim();

replace(oldChar:char,newChar:char):String;

说明:返回用一个新字符替换这个字符串中所有和它匹配的字符的新字符串

replaceAll(oldString:String,newString:String):String;

说明:返回用一个新子串替换这个字符串中所有和它匹配的子串之后的新字符串

replaceFirst(oldString:String,newString:String):String;

说明:返回用一个新子串替换这个字符串中第一个和它匹配的子串之后的新字符串

split();

说明:返回用定界符分隔的子串所构成的一个字符串数组

举例:

String tokens[] = "Java#Perl#HTML".split("#",0);

for(int i=0;i<tokens.length;i++)

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

输出:

Java

Perl

HTML

 

5.找出字符串中的某个字符或者子串

indexOf(ch:char):int;

indexOf(ch:char,fromIndex:int):int;

indexOf(s:String):int;

indexOf(s:String,fromIndex:int):int;

lastIndexOf(ch:int):int;

lastIndexOf(ch:int,fromIndex:int):int;

lastIndexOf(s:String):int;

lastIndexOf(s:String,fromIndex:int):int;

说明:返回 String 对象中子字符串最后出现的位置

以上如果没有找到某个字符或者子字符串,则返回 -1

 

Example:

String s1 = "Welcome to Java";

s1.indexOf('j');

s1.indexOf("to");

s1.lastIndexOf('a');

s1.lastIndexOf("o", 8);

结果:

-1

8

14

4

 

对最后一个解释下:

String s1 = "Welcome to Java";

System.out.println(s1.lastIndexOf("o", 8));

输出结果为:4

从左往右数到第八位也就是到字母”t”,然后从t开始从右往左查找”o”, 返回 String 对象中子字符串最后出现的位置也就是4了。

若改为:

String s1 = "Welcome to Java";

System.out.println(s1.lastIndexOf("o", 9));

输出结果为:9

 

6.字符串与数组之间的转换

字符串-->字符数组

toCharArray();

字符数组-->字符串

valueOf(c:char):String;

valueOf(data:char[ ]):String;

valueOf(d:double):String;

valueOf(f:float):String;

亲,如果我要把一个字符串转换成double型值或int型值呢?

用下面的好啦:)

Double.parseDouble(str);

Integer.parseInt(str);


转至:http://www.taobaotest.com/blogs/2260

415°/4140 人阅读/1 条评论 发表评论

文晶  2012-12-12

不错哦


登录 后发表评论