java性能优化一

白色玫瑰 程序猿

时间: 2023-05-22 阅读: 1 字数:6150

{}
1. 谨慎对待Java的循环遍历 Java中的列表遍历可比它看起来要麻烦多了。就以下面两段代码为例: A: private final List _bars; for(Bar bar : _bars) { //Do important stuff } B: private final List _bars...
  1. 谨慎对待Java的循环遍历

Java中的列表遍历可比它看起来要麻烦多了。就以下面两段代码为例:

A:

   private final List<Bar> _bars;  
   for(Bar bar : _bars) {  
      //Do important stuff  
   }  

B:

   private final List<Bar> _bars;  
   for(int i = 0; i < _bars.size(); i++) {  
      Bar bar = _bars.get(i);  
      //Do important stuff  
   }  

代码A执行的时候会为这个抽象列表创建一个迭代器,而代码B就直接使用 get(i) 来获取元素,相对于代码A省去了迭代器的开销。 实际上这里还是需要一些权衡的。代码A使用了迭代器,保证了在获取元素的时候的时间复杂度是 O(1) (使用了 getNext() 和 hasNext() 方法),最终的时间复杂度为 O(n) 。但是对于代码B,循环里每次在调用 _bars.get(i) 的时候花费的时间复杂度为 O(n) (假设这个list为一个 LinkedList),那么最终代码B整个循环的时间复杂度就是 O(n^2) (但如果代码B里面的list是 ArrayList, 那 get(i) 方法的时间复杂度就是 O(1)了)。所以在决定使用哪一种遍历的方式的时候,我们需要考虑列表的底层实现,列表的平均长度以及所使用的内存。如果我们需要优化内存,再加上 ArrayList 在大多数情况下查找的时间复杂度为 O(1) ,可以选择代码B所使用的方法。 2.在初始化的时候预估集合的大小

从Java的这篇 <a href="https://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html">文档</a>我们可以了解到: “一个HashMap 实例有两个影响它性能的因素:初始大小和加载因子(load factor)。 当哈希表的大小达到初始大小和加载因子的乘积的时候,哈希表会进行 rehash操作。如果在一个HashMap 实例里面要存储多个映射关系时,我们需要设置足够大的初始化大小以便更有效地存储映射关系而不是让哈希表自动增长让后rehash,造成性能瓶颈。”

常常碰到需要遍历一个 ArrayList 并将这些元素保存到 HashMap 里面去,将这个 HashMap 初始化预期的大小可以避免再次哈希所带来的开销。初始化大小可以设置为输入的数组大小除以默认加载因子的结果值(这里取0.7):

优化前的代码:

   HashMap<String,Foo> _map;  
   void addObjects(List<Foo> input)  
   {  
     _map = new HashMap<String, Foo>();  
     for(Foo f: input)  
     {  
      _map.put(f.getId(), f);  
     }  
   }  

优化后的代码:

   HashMap<String,Foo> _map;  
   void addObjects(List<Foo> input)  
   {  
      _map = new HashMap<String, Foo>((int)Math.ceil(input.size() / 0.7));  
      for(Foo f: input)  
      {  
         _map.put(f.getId(), f);  
      }  
   }  
 3. 延迟表达式的计算 
   

在Java中,所有的方法参数会在<a href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7.4">方法调用之前</a>,只要有方法参数是一个表达式的都会先对这个表达式进行计算(从左到右)。这个规则会导致一些不必要的操作。考虑到下面一个场景:使用<a href="http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ComparisonChain.html">ComparisonChain</a>比较两个 Foo 对象。使用这样的比较链条的一个好处就是在比较的过程中只要一个 compareTo 方法返回了一个非零值整个比较就结束了,避免了许多无谓的比较。例如现在这个场景中的要比较的对象最先考虑他们的score, 然后是 position, 最后就是 _bar 这个属性了:

   public class Foo {  
      private float _score;  
      private int _position;  
      private Bar _bar;  
      
      public int compareTo (Foo other) {  
         return ComparisonChain.start().  
            compare(_score, other.getScore()).  
            compare(_position, other.getPosition()).  
            compare(_bar.toString(), other.getBar().toString()).  
            result;  
      }  
   }  

但是上面这种实现方式总是会先生成两个 String 对象来保存 bar.toString() 和other.getBar().toString() 的值,即使这两个字符串的比较可能不需要。避免这样的开销,可以为Bar 对象实现一个 comparator:

   public class Foo {  
      private float _score;  
      private int _position;  
      private Bar _bar;  
      private final BarComparator BAR_COMPARATOR = new BarComparator();  
      
      public int compareTo (Foo other) {  
         return ComparisonChain.start().  
            compare(_score, other.getScore()).  
            compare(_position, other.getPosition()).  
            compare(_bar, other.getBar(), BAR_COMPARATOR).  
            result();  
      }  
      private static class BarComparator implements Comparator<Bar> {  
         @Override  
         public int compare(Bar a, Bar b) {  
            return a.toString().compareTo(b.toString());  
         }  
      }  
   }  

4. 提前编译正则表达式

字符串的操作在Java中算是开销比较大的操作。还好Java提供了一些工具让正则表达式尽可能地高效。动态的正则表达式在实践中比较少见。在接下来要举的例子中,每次调用 String.replaceAll() 都包含了一个常量模式应用到输入值中去。因此我们预先编译这个模式可以节省CPU和内存的开销。

优化前:

   private String transform(String term) {  
      return outputTerm = term.replaceAll(_regex, _replacement);  
   }  

优化后:

   private final Pattern _pattern = Pattern.compile(_regex);  
   private String transform(String term) {  
      return outputTerm = _pattern.matcher(term).replaceAll(_replacement);  
   }  

5. 尽可能地缓存Cache it if you can

将结果保存在缓存里也是一个避免过多开销的方法。现在已经有多种LRU(Least Recently Used )缓存算法实现。

6. String的intern方法有用,但是也有危险

String 的 intern 特性有时候可以代替缓存来使用。

从这篇<a href="https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern%28%29">文档</a>,我们可以知道:

“A pool of strings, initially empty, is maintained privately by the class String. When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned”.

这个特性跟缓存很类似,但有一个限制,你不能设置最多可容纳的元素数目。因此,如果这些intern的字符串没有限制(比如字符串代表着一些唯一的id),那么它会让内存占用飞速增长。

原文地址:https://blog.csdn.net/qq359605040/article/details/45042341?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168474999716782427485144%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168474999716782427485144&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-13-45042341-null-null.142^v87^insert_down1,239^v2^insert_chatgpt&utm_term=java%E4%BC%98%E5%8C%96

本文章网址:https://www.sjxi.cn/detil/b8a0a2f9a8d94bf7a4549ad4ac2e0ab4

最新评论

当前未登陆哦
登陆后才可评论哦

湘ICP备2021009447号

×

(穷逼博主)在线接单

QQ: 1164453243

邮箱: abcdsjx@126.com

前端项目代做
前后端分离
Python 爬虫脚本
Java 后台开发
各种脚本编写
服务器搭建
个人博客搭建
Web 应用开发
Chrome 插件编写
Bug 修复