String最为java中最重要的数据类型。字符串是软件开发中最重要的对象之一,通常,字符串对象在内存中总是占据着最大的空间块。所以,高效处理字符串,将提高系统的整个性能。
在java语言中,String对象可以认为是char数组的衍生和进一步的封装。它的主要组成部分是:char数组、偏移量和string的长度。char数组表示string的内容,它是string对象所表示字符串的超集。String的真实内容还需要偏移量和长度在这个char数组中进一步定位和截取。(查看java源代码可以看到char数组、偏移量和长度定义)
String对象的三个基本特点:
1、不变性;String对象一旦生成,则不能对它进行改变。String对象的这个特性可以泛指为不变模式,即一个对象的状态在对象被创建之后就不再发生变化。另外多说一点,不变模式主要作用在当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁的等待时间,从而大幅度的提高系统的性能。
2、针对常量池的优化;当两个string对象拥有相同的值的时候,他们只引用常量池中同一个拷贝。当同一个字符串反复出现时,可以大幅度的节省内存空间。
3、类的final的定义。final类的String对象在系统中不可能有任何子类,这是对系统安全性的保护。
String
类包括的方法可用于检
- 截取子字符串
截取子字符串是java中最常用的操作之一,在java中提供了两个截取子字符串的方法:
1 substring(int beginIndex, int endIndex)
2 substring(int beginIndex)
查看substring(int beginIndex, int endIndex)的源码:
View Code
在方法的最后有一个返回一个新建的String对象,查看其构造函数:
View Code
在源码中,这是一个包作用域的构造函数,其目的是为了能高效且快速的共享String内的char数组对象。但是这种通过偏移量来截取字符串的方法中,String原生内容的value数组会被复制到新的子字符串中。设想,如果子字符串长度很短,但是原来字符串长度却很长,那么截取的子字符串包含原生字符串中的所有内容,并占据了相应的内存空间,而仅仅通过偏移量和长度来决定自己的实际取值。这种方式提高了运算速度却浪费了空间,是一种以时间换空间的解决方案。
实例代码:
1 public class Test {
2
3 public static void main(String[] args) {
4 List<String> hander = new ArrayList<String>();
5 for (int i = 0; i < 100000; i++) {
6 HugeStr str = new HugeStr();
7 //ImpHugeStr str = new ImpHugeStr();
8 hander.add(str.getSubString(0, 5));
9 }
10 }
11
12 }
13
14 static class ImpHugeStr {
15 private String str = new String(new char[10000]);
16 public String getSubString(int begin,int end){//截取子字符串并且重新生成新的字符串
17 return new String(str.substring(begin, end));
18 }
19 }
20 static class HugeStr {
21 private String str = new String(new char[10000]);
22 public String getSubString(int begin,int end){//截取子字符串
23 return str.substring(begin, end);
24 }
25 }
ImpHugeStr使用没有内存泄漏的String构造函数重新生成String对象,使得由subString()方法返回的,存在内存泄漏的String对象失去强引用,从而被垃圾回收机制当中垃圾回收,从而保证了系统内存的稳定。
subString()之所以引起内存泄漏是因为使用了String(int offset,int count,char[] value)构造函数,此构造函数采用以空间换时间的策略。
以上构造函数在java1.5中采用,在java1.6中采用以下构造函数:
View Code
代码是采用this.value = Arrays.copyOfRange(value, offset, offset+count),使value失去强引用被垃圾回收,所有也不存在内存溢出的问题。
- 字符串的分割和查找
字符串的分割和查找也是字符串处理中最常用的方法之一,字符串分割是将原始字符串根据某一个分割符,切割成一组小的字符串。
字符串的分割大致有三种方法:
1、最原始的字符串分割:
split()是最原始的字符串分割方法,但是它提供了非常强大的字符串分割功能。传入的参数可以是一个正则表达式,从而实现复杂逻辑的字符串分割。但是,对于简单字符串的风格,split()的性能却不尽如人意。所有在性能敏感的系统使用是不可取的。
2、使用StringTokenizer类分割字符串:
StringTokenizer是JDK提供的专门用于字符串分割子串的工具类。
其构造函数:new StringTokenizer(String str,String delim),其中str参数为要处理的分割字符串,delim是分割符。当一个StringTokenizer对象生成后,可以通过nextToken()方法得到下一个要分隔的字符串,通过hasMoreTokens()方法可以知道是否有更多的子字符串需要处理。StringTokenizer的更多具体用法查看API。
3、自己实现字符串分割方式:
通过使用String对象的两个方法:indexOf()和subString()。前面提到subString()是通过以空间换时间的策略,它的执行速度很快,同时indexOf也是一个执行效率特别快的方式。
实现代码:
1 String str="";//待分割字符串
2 while(true){
3 String subStr = null;
4 int j = str.indexOf(";");//分割符
5 if(j<0) break;
6 subStr = str.subString(0,j);//截取子字符串
7 str = str.subString(j+1);//剩下待截取子串
8 }
三种方式的比较,第一种split功能强大,但是效率最差;第二种StringTokenizer的效率由于split,因此可以使用StringTokenizer的地方一般尽量使用StringTokenizer;第三种执行效率最好,但是可读性比较差。
另外,String对象还提供了一个charAt(int index)方法,它返回指定字符串中位于index的字符,它的功能和indexOf()相反,但是它的执行效率同样十分高。例如,经常在项目组遇到判断字符串以XX开头或XX结尾的问题,我们第一想到的是String对象提供的startWith()和endWith()方法,如果改用charAt()方法实现,效率会快很多。例如:"abcd".startWith("abc"),改为"abcd".charAt(1) == "a"&&"abcd".charAt(1) == "b"&&"abcd".charAt(1) == "c"在大量使用的场景下会提高系统效率。
查序列的单个字符、比较字符串、搜索字符串、提取子字符串、创建字符串副本并将所有字符全部转换为大写或小写等,熟练使用这些方法在企业开发中会有很大的帮助