• 字符串优化处理


    字符串优化处理

    一)、字符串的内部结构

    1)、char数组

    表示String的内容,所有字符串的超集。
    

    2)、offset偏移

    3)、count长度

    注:String的真实内容由offset和count进行定位和截取。

    二)、字符串的特性

    1)、不变性

    当一个对象被多线程共享,并且频繁使用时 ,可以省略同步和锁等待时间。
    

    2)、字符串常量池

    3)、String由final修饰不能被继承

    三)、subString()方法的内存泄露

    1. subString(int beginIndex)

    2. subString(int beginIndex, int endIndex)

    subString()源码剖析:

    1)、JDK1.6版本实现:

    通过offset = offset + beginIndex, count = endIndex - beginIndex,采用时间换空间的方式重新生成一个字符串。

    new String(int offset, int count, char[] value);

    每生成一个新的子字符串都是将原char[]value数组赋值到新的子字符串中,然后通过偏移量和长度来决定实际的取值。

    此时,若有大量的长字符串进行截取就会出现内存泄露的情况。

    改进:

    new String(subString(0,1)):

    重新生成一个字符串,此时字符串的value值则为子字符串的value值,而new String(offset, count , cahr[] value)中生成的字符串因为没有被使用,则被GC回收,解决了内存泄露的问题。

    2)、在JDK1.7版本后对该功能进行了改进,解决了内存泄露的问题

    new String(int offset, int count, char[] value)

    实现:通过offset和count重新生成一个数组,并根据偏移量将原数组对应的数据复制到新数组中。

    四)、字符串的分割和查找

    字符串的三种分割方式:

    1)、split(String regex)

    2)、StringTokenier(String str, String delim)

        hasMoreTokens();
    
        nexToken();
    

    3)、自定义字符串分割

    通过indexOf(int ch)和subString()
    

    三种分割方法的速度比较:

    split < StringTokenier < 自定义

    (参考书籍是这么说,但通过自测试发现自定义截取子符串耗时却长得多)

    /**
     * 字符串分割
     */
    public class StringSplit {
        public static void main(String[] args) {
            String orgStr = null;
            StringBuffer sb = new StringBuffer();
            for(int i = 0; i < 10000; i++){
                sb.append(i);
                sb.append(";");
            }
            orgStr = sb.toString();
            //对orgStr字符串进行分割
            Long start = System.currentTimeMillis();
    
            /**
             * 使用split分割
             */
            for(int i = 0; i < 10000; i++){
                orgStr.split(";");
            }
            //使用split分割耗时2655
            System.out.println(System.currentTimeMillis()-start);
    
            /**
             * 使用StringTokenier分割
             */
            StringTokenizer st = new StringTokenizer(orgStr,";");
            Long start1 = System.currentTimeMillis();
            for(int i = 0; i < 10000; i++){
                while(st.hasMoreElements()){
                    st.nextToken();
                }
                st = new StringTokenizer(orgStr,";");
            }
            //使用StringTokenizer分割耗时2719秒,在使用个StringTokenizer分割时还有创建和销毁StringTokenizer对象的时
            // 因此总体来说,使用StringTokenizer的性能比split的性能要好
            //耗时2719
            System.out.println(System.currentTimeMillis()-start1);
    
            /**
             * 使用indexOf和subString 来分割
             */
            String temp = orgStr;
            Long start2 = System.currentTimeMillis();
            for(int i = 0; i < 10000; i++){
                while(true) {
                    String splitStr = null;
                    //获取第一个;所在的位置
                    int j = temp.indexOf(';');
                    if(j < 0){
                        break;
                    }
                    splitStr = temp.substring(0, j);
                    temp = temp.substring(j + 1);
                }
                temp = orgStr;
            }
            //耗时413933
            System.out.println(System.currentTimeMillis()-start2);
        }
    }
    

    五)、StringBuffer和StringBuilder

    字符串的累加

    == 静态字符串的累加 ==:

    String result = "String" + "and" + "String" + "append";
    
    StringBuffer sb = new StringBuffer();
    sb.append("String");
    sb.append("and");
    sb.append("String");
    sb.append("append");
    

    我们想象中result拼接程序的执行过程应该是Sting + and 两个字符串生一个Stringand,Stringand再个String结合生成StringandString,生成多个字符串实例,最后的到result拼接结果。

    sb中程序执行过程是,产生一个StringBuffer实例,然后往sb容器中添加数据。

    测试两端代码的执行时间,发现result拼接的耗时小于sb添加的耗时,为什么呢?

    通过反编译发现,在编译时期,java虚拟机自动将String result = "String" + "and" + "String" + "append";编译成String result = "StringandStringappend";直接生成拼接结果对象,而sb还要进行append操作,因此,对于显示的(静态的)字符串拼接性能优于StringBuffere拼接。

    == 变量字符串的累加 ==:

    String str1 = "String";
    String str2 = "and";
    String str3 = "String";
    String str4 = "append";
    String result = str1 + str2 + str3 + str4;
    
    StringBuffer sb = new StringBuffer();
    sb.append("String");
    sb.append("and");
    sb.append("String");
    sb.append("append");
    

    测试两端代码的执行速度,发现两端代码的执行速度几乎一样

    原因:

    通过反编译发现代码段1 的编译结果为

    String str1 = "String";
    String str2 = "and";
    String str3 = "String";
    String str4 = "append";
    String result = new StringBuffer(str1).append(str2).append(str3).append(str4).toString();
    

    结论:java虚拟机自动在编译时期就对字符串的累加进行了优化,对于静态字符串的累加在编译时期自动合成一个单独的长字符串,对于变量字符串的累加,使用StringBuffer对象来完成累加操作。

    另一种字符串累加方式:

    String str1 = "String";
    String str2 = "and";
    String str3 = "String";
    String str4 = "append";
    String result = str1.concat(str2).concat(str3).concat(str4);
    

    字符串的累加效率:

    += / + < concat < StringBuffer/StringBuilder

    StringBuffer和StringBuilder的区别:

    1)、StringBuffer

    线程同步,保证线程安全,但同步方法消耗一定的系统资源。
    

    2)、StringBuilder

    线程不同步,效率高,当线程不安全。
    

    3)、StringBuffer和StringBuilder底层都是一个char[]类型的数组,默认数组长度为16个字节。

    4)、当创建StringBuffer和StringBuilder对象时,为了提高系统的性能,最好声明对象的长度。

          因为,当所需长度大于内部数组的长度会会对内部数组进行扩容(扩容后容量翻倍),并将原数据复制到扩容后的新数组中,存在大量的内存复制操作,消耗资源。
    

    使用场景:不考虑线程安全时使用StringBuilder, 考虑线程安全时使用StringBuffer

    金麟岂能忍一世平凡 飞上了青天 天下还依然
  • 相关阅读:
    vim可以打开,gvim无法打开
    Ubuntu用apt-get安装时依赖包无法安装
    如何用mm、mmm编译android中的模块
    装饰模式简单的代码
    FileWriter和FileReader简单使用
    TCP/IP、Http、Socket的区别--特别仔细
    surface实例-小球弹起事例
    android中图片的三级缓存cache策略(内存/文件/网络)
    接口回调
    大公司的Java面试题集
  • 原文地址:https://www.cnblogs.com/Auge/p/11646442.html
Copyright © 2020-2023  润新知