摘要:
继续分析Java程序优化的一些小技巧,关于其他的Java程序优化技巧,可以点这里看我的上一篇博客
字符串优化处理
概述:Java的String类底层使用一个final char[] + 偏移量 + count长度变量,来实现的。它有三个特点:不变性,针对常量池的优化,类的final定义
不变性是指:当String对象被创建出来以后,它就是不可变的了,好处是当它需要被多个线程共享、频繁访问的时候不需要加锁,提高性能;
针对常量池的优化是指:当new String的时候,如果常量池没有这个对象,会现在常量池创建一个该对象,然后再内存中创建一个该实例,如果下次又要创建改变量的时候,就只在内存中创建实例,然后该实例再指向常量池的字符串。
优化细节:
1.subString()方法存在内存泄漏(在jdk1.6及之前的问题,jdk1.7及以上已经解决)
这里看实现的源码:
//jdk1.8源码
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//这里返回的是在原来的字符串的基础上新建String对象,只修改偏移量和长度来
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
所以当我们原来的字符串很长的时候,用subString后,其实是把原来的字符串复制一遍,然后通过修改它的偏移量和长度计数器来实现的;因此新字符串中保存了很多没用的字符串,这样存在很多内存的浪费,使用不慎就会出现内存溢出。举个例子
String st1 = "asdfghjkl";
// ==> char[] = {'a','s','d','f','g','h','j','k','l'} 偏移量 = 0 长度计数器 = 9
String st2 = st1.substring(2,5);
// ==> char[] = {'a','s','d','f','g','h','j','k','l'} 偏移量 = 2 长度计数器 = 3
那么为什么jdk开发者还要这样做呢?
开发者采取了一种空间换时间的策略,因为通过subString()最终是调用了操作系统本身的数组复制方法,速度非常的快,为的是提高程序运行的速度,这里就牺牲了空间。所以这里我们要多加注意,如果我们对一个长字符串多次用subString方法的时候就要留意可能会存在内存溢出。
解决方案:
String st1 = "asdfghjkl";
String st2 = new String(st1.substring(2,5));
//这样会把调用new String的另一个构造方法重新建立一个字符串,这个字符串就不会包含之前无用的那些部分,从而解决内存泄漏问题
2.字符串分割和查找:
在做字符串分割的时候,我们经常使用string.spilt()这个方法,虽然这个方法是功能强大,使用简单,但是其速度不快,在需要频繁使用分割功能的时候优先使用StringTokenizer这个类,是专门用来左字符串分割的。下面这个方法返回的就是一个分割好的字符串数组:
/**
* jdk中专门用来处理字符串分割的子串工具,
* 如果能用这个就不要用split()了
* @param orgStr
* @param delim
* @return
*/
public static String[] getSplit2(String orgStr, char delim){
StringTokenizer st = new StringTokenizer(orgStr, String.valueOf(delim));
ArrayList<String> list = new ArrayList<String>();
while (st.hasMoreTokens()){
list.add(st.nextToken());
}
return (String[])list.toArray();
}
关于分割:如果想更快,还可以结合indexOf()和subString()自己写一个分割方法,这里就不多做介绍了详情可见我的github代码库,里面都有实现。
用IndexOf()自实现一个startWith()方法和endWith()
因为indexOf()速度非常快,我的实现:(endWith()实现见github)
/**
* 通过charAt()来自定义一个startWtih和endWith功能函数会比原生的快很多
*/
/**
* 判断orgStr是否以prefix开头
* @param orgStr
* @param prefix
* @return
*/
public static boolean startWith(String orgStr, String prefix){
for (int i = 0; i < prefix.length(); i++){
if (prefix.charAt(i) != orgStr.charAt(i)){
return false;
}
}
return true;
}
3.用StringBuider或StringBuffer替换String做字符串拼接
PS:StringBuilder和StringBuffer功能一样,但是后者是用于多线程的,做线程同步的操作,因此速度比前者慢。
对于拼接优先使用StringBuilder,因为String的不变性,导致每次拼接都需要新建一个对象,在内存和速度上是不快的,(尽管JVM对String拼接在编译上做了很多优化,让本该很慢的操作不太慢。但是还是不建议使用,因为这些不可控)
目前先介绍这多,持续更新中。
Java代码的github仓库:https://github.com/aa792978017/JavaLearning