程序员人生 网站导航

java性能优化技巧二

栏目:php教程时间:2015-04-15 08:59:48
之前整理过1篇java性能优化的博客,链接java性能优化1,今天补充几个

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履行的时候 会为这个抽象列表创建1个迭代器,而代码B就直接使用 get(i) 来获得元素,相对代码A省去了迭代器的开消。
  • 实际上这里还是需要1些权衡的。代码A使用了迭代器,保证了在获得元素的时候的时间复杂度是 O(1) (使用了 getNext() 和 hasNext() 方法),终究的时间复杂度为 O(n) 。但是对代码B,循环里每次在调用 _bars.get(i) 的时候花费的时间复杂度为 O(n)  (假定这个list为1个 LinkedList),那末终究代码B全部循环的时间复杂度就是 O(n^2)  (但如果代码B里面的list是 ArrayList, 那 get(i) 方法的时间复杂度就是 O(1)了)。所以在决定使用哪种遍历的方式的时候,我们需要斟酌列表的底层实现,列表的平均长度和所使用的内存。如果我们需要优化内存,再加上 ArrayList 在大多数情况下查找的时间复杂度为 O(1) ,可以选择代码B所使用的方法。

  • 2.在初始化的时候预估集合的大小

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


常常碰到需要遍历1个 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中,所有的方法参数会在方法调用之前,只要有方法参数是1个表达式的都会先对这个表达式进行计算(从左到右)。这个规则会致使1些没必要要的操作。斟酌到下面1个场景:使用ComparisonChain比较两个 Foo 对象。使用这样的比较链条的1个好处就是在比较的进程中只要1个 compareTo 方法返回了1个非零值全部比较就结束了,避免了许多无谓的比较。例如现在这个场景中的要比较的对象最早斟酌他们的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 对象实现1个 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提供了1些工具让正则表达式尽量地高效。动态的正则表达式在实践中比较少见。在接下来要举的例子中,每次调用 String.replaceAll() 都包括了1个常量模式利用到输入值中去。因此我们预先编译这个模式可以节省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

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


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

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

从这篇文档,我们可以知道:

“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”.

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


作者:jason0539

博客:http://blog.csdn.net/jason0539(转载请说明出处)

扫码关注我微信公众号

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐