《JAVA程序性能优化》总结

设计优化
  1. 单例模式:延时加载(内部类)。反射和序列化会破坏单例
  2. 代理模式:延时加载
    • 静态代理:包括主题接口、真实主题、代理类、main。初始化时使用代理,真正使用时再通过代理加载真实主题。
    • 动态代理:jdk动态代理、CGLIB、javassist基于动态代码的代理、ASM。
    • 动态加载过程(以CGLIB为例):
      • 根据指定的回调类生成class字节码,并保存在byte数组中。
      • 通过反射,调用ClassLoader.defindClass将字节码定义为类。
      • 使用反射机制生成该类的实例。
    • 经典实现:AOP。AOP的实现方式有以下几种方式:
      • 静态织入:在编译期将切面织入到目标文件中。
      • jdk动态代理:运行期为接口生成代理类,再将切面织入代理类中。
      • CGLIB和javassist动态字节码:运行期,在目标加载后,动态构建字节码文件生成目标类的子类,将切面织入子类中。
  3. 享元模式:
    • 复用大对象,节省内存和创建的时间。
    • 和对象池的不同在于:享元对象都是不同的,各自有各自的含义和用途,而对象池的对象都是等价的,如数据库连接池中的连接。
  4. 装饰者模式:
    • 可以将功能组件和性能组件分开,需要再结合起来。
    • 设计原则:使用委托,少用继承。
    • 经典例子:OutputStream和InputStream。
  5. 观察者模式
常用优化组件和方法
  1. 缓冲:
    • 协调上层组件和下层组件的性能差异,最常用于提高I/O的速度。
    • 经典例子:写文件操作,FileWriter和BufferedWriter,使用缓冲区的writer性能会提升一倍。
  2. 缓存
  3. 对象复用—池
    • 只有对重量级对象使用对象池技术才能提高系统性能,对轻量级对象使用对象池,反而会降低系统性能。
    • 在Apache中,已经提供了一个Jakarta Commons Pool对象池组件可使用。
  4. 并行替代串行:数据迁移
  5. 负载均衡
  6. 时间换空间:CPU与内存
  7. 空间换时间:缓存
java程序优化
  1. String:
    • 由char数组、偏移量和string长度组成。
    • 其真实长度由偏移量和长度在这个char数组中进行定位和截取。
    • substring容易导致内存泄漏。
    • 对于字符串相加,如果直接使用string相加,编译期会自动优化成StringBuilder,所以会有所优化。但若是在for循环中string相加,编译期没有那么只能,所以每次循环都会为string创建一个新的StringBuilder,所以相对而言,直接用string相加还是不如直接用StringBuilder来append。
  2. List(ArrayList、Vector、LinkedList):
    • ArrayList和Vector —> AbstractList —> List。
      LinkedList —> AbstractSequenceList —> AbstractList —> List。
    • ArrayList和Vector都是基于数组实现,封装了对内部数组的操作。
      LinkedList使用循环双向链表数据结构。前驱表项(最后一个元素) <— 当前元素(header) —> 后驱表项(第一个元素)。
    • 普通新增操作(即直接新增到最后)。
      //ArrayList
      public boolean add(E e){
      encureCapacity(size+1);//确保内部数组有足够空间,不够则进行扩容,1.5倍。性能取决于这个方法
      elementData[size++] = e;//将元素加入到数组的末尾,完成添加
      return true;
      }
      //LinkedList
      private Entry(E) addBefore(E e, Entry entry){
      //下面三行代码是性能消耗的关键点
      Entry newEntry = new Entry(e, entry, entry.previous);//创建新的元素
      newEntry.previous.next = newEntry;//将前驱表项的下一个元素指向当前新增元素
      newEntry.next.previous = newEntry;//将后驱表项的上一个元素指向当前新增元素
      size++;
      modCount++;
      return newEntry;
      }
    • 根据下标新增到指定位置
      ArrayList:每次插入操作,都会进行数组复制,下标越前,性能越差。
      LinkedList:在哪里插入性能都一样。
    • 删除指定位置
      ArrayList:同新增一样,都需要进行数组的复制,从尾到头性能逐渐提升。
      LinkedList:需要遍历,下标为中间是性能最差,需要遍历1/2的列表元素。
    • foreach运行时会被编译期解析成迭代器,反编译的代码看到还多了一步赋值的操作,所以三种循环的性能排序为for循环 > 迭代器 > foreach。
  3. Map:
    • properties —> HashTable —> Dictionary、Map。
      HashMap —> AbstractMap —> Map。
      TreeMap —> AbstractMap —> Map。
      LinkedHashMap —> HashMap —> AbstractMap —> Map。
    • HashTable不允许key或value使用null值,但HashMap可以。
    • HashMap原理:将key做hash算法,然后将hash值映射到内存地址,直接取得key所对应的数据。底层数据结构是数组,内存地址即数组的下标索引。
    • 为何HashMap是高性能的:
      • hash算法高效(多采用navive本地方法和位运算)。
      • hash值到内存地址(数组索引)的算法高效(根据hash值和数组长度 按位与计算)。
      • 根据内存地址(数组索引)可以直接取得对应的值。
    • hash算法和查找的源码:
      • hash算法:
        int hash = hash(key.hashCode());//计算key的hash值
        public native int hashCode();//可以重写,性能关键点,所以重写的hashCode方法是否冲突很重要
        static int hash(itn h){//基于位运算
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
        }
      • 查找内存地址的算法:
        int i = indexFor(hash, table.length);
        static int indexFor(int h, int length){
        return h & (length-1);
        }
    • HashMap高性能的注意条件:
      • hashCode()方法的实现,要尽可能减少冲突,这样子对HashMap的操作就近乎对数组的随机访问。若冲突多,则相当于退化成几个链表,等价于遍历链表,性能很差。一般hashCode的生成可以直接使用eclipse IDE提供的方法,或者引入第三方库如Apache commons来生成。
      • 容量参数,HashMap的扩容会遍历整个HashMap,对里面的数据进行重新计算在新数组的位置,所以应尽量避免扩容,在初始化的时候就估算好大概的容量。
      • 负载因子 = 元素个数 / 内部数组大小,默认为0.75,尽量不要大于1,这样子会带来冲突。
    • HashMap的表项结构,实际上是一个链表的数组。
      Entry1(每个Entry包括key、value、next、hash)
      Entry2


      Entryn —> Entryn1 —> Entryn2(hash冲突的链表结构)。
    • LinkedHashMap:维护了元素次序表的HashMap,在每个Entry对象中添加了before、after两个属性。有两种排序类型,按照元素进入集合的顺序或者被访问的先后顺序排序。
    • TreeMap:基于匀速的固有顺序排序(由Comparator或者Comparable确定)。其内部实现是基于红黑树,是一种平衡查找树,性能要优于平衡二叉树,可以在O(log n)时间内查找、插入和删除。
  4. Set:
    HashSet、LinkedHashSet、TreeSet都只是对应的Map的一种封装,所有操作都委托给HashMap对象完成。
  5. NIO:
    • 与流式I/O不同,它是基于块(Block)的。
    • 最重要的两个组件:缓冲Buffer和通道Channel。通道表示缓冲数据的源头或目的地,用于向缓冲读取或者写入数据,是访问缓冲的接口。应用程序不能直接对Channel进行读写操作,而必须通过Buffer来进行。
    • NIO进行文件复制的例子:
      public static void nioCopyFile(String resource, String destination){
      FileInputStream fis = new FileInputStream(resource);
      FileOutputStream fos = new FileOutputStream(destination);
      FileChannel readChannel = fis.getChannel();//读文件通道
      FileChannel writeChannel = fos.getChannel();//写文件通道
      ByteBuffer buffer = ByteBuffer.allocate(1024);//读入数据缓存
      while(true){
      buffer.clear();
      int len = readChannel.read(buffer);
      if(len == 1){
      break;
      }
      buffer.flip();
      writerChannel.write(buffer);
      }
      readChannel.close();
      writeChannel.close();
      }
    • Buffer3个重要的参数:position(位置)、capacity(容量)、limit(上限)。当执行flip()操作会将写模式转换为读模式,并将limit设置为当前position和将position置为0。
    • 三种文件流操作性能对比:基于Buffer的性能比普通的基于流的性能要高一倍,基于Buffer并将文件映射到内存的性能要高出一个数量级。
并行程序开发及优化
  1. Future模式(可用于一个大方法中,某些小方法比较耗时,则可以将这些小方法用这种模式处理)
    • 核心在于去除主函数等待时间,并使得原本需要等待的时间段可以用于处理其他业务,充分利用计算机资源。
    • jdk的并发包中已经内置了一种Future模式的实现,关键是callable接口的call()方法,重载定义业务逻辑。
  2. Master-Worker模式(可用于数据迁移前的准备数据,多进程收集数据并异步计算结果)
    • Master进程为主要进程,维护了一个worker进程队列、子任务队列和子结果集。Worker进程队列中的worker进程不停地从任务队列中提取要处理的子任务,并将子任务的处理结果写入结果集。
    • 可以使用ForkJoinPool框架。
  3. 生产者-消费者模式
    • 模式架构图:生产者 —> 内存缓冲区 —> 消费者。
    • 生产者Producer:用于提交用户请求,提取用户任务,并装入内存缓冲区。
      消费者Consumer:在内存缓冲区中提取并处理任务。
      内存缓冲区BlockingQueue:缓存生产者提交的任务或数据,供消费者使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值