Java程序性能提高篇-----2

摘要:

  继续分析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

猜你喜欢

转载自blog.csdn.net/aa792978017/article/details/88943715