java性能优化技巧二

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

大家好,又见面了,我是全栈君。

之前整理过一篇java性能优化的博客,链接
java性能优化一,今天补充几个

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的这篇 文档我们可以了解到: “一个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中,所有的方法参数会在方法调用之前,只要有方法参数是一个表达式的都会先对这个表达式进行计算(从左到右)。这个规则会导致一些不必要的操作。考虑到下面一个场景:使用ComparisonChain比较两个 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 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),那么它会让内存占用飞速增长。

作者:jason0539

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

扫码关注我微信公众号

java性能优化技巧二

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/121328.html原文链接:https://javaforall.net

(0)
上一篇 2022年3月11日 下午2:00
下一篇 2022年3月11日 下午2:00


相关推荐

  • Oracle 恢复数据到某个时间节点

    Oracle 恢复数据到某个时间节点–注意:恢复的时间点与当前时间节点表结构需要一致,truncate的数据无法恢复–1.创建临时表保存该时间节点表的数据createtabletemp_table–临时表asselect*fromT_PM_ParamItem–原表asoftimestampto_timestamp(‘2018-01-1211:11:11’,’yyyy-mm-ddhh24…

    2026年2月25日
    4
  • csgo电脑人怎么控制(csgo守护模式)

    网吧以浓厚的电竞氛围,吸引了很多游戏玩家前去消费。但常去网吧的人会发现:玩穿越火线、英雄联盟、绝地求生甚至守望先锋的人都不在少数,却单单少了CSGO玩家的身影,这是为什么?随着前两年绝地求生的“吃鸡”热潮来袭,网吧早已升级了电脑配置,绝大多数游戏玩家都可流畅地进行自己喜欢的游戏,难道CSGO玩家如何不合群?让我们来熟悉一下进入网吧后的一系列操作就可以得到结论了!去网吧:穿越火线/英雄联盟/绝地求生…

    2022年4月12日
    224
  • VC++常用功能开发「建议收藏」

    VC++常用功能开发「建议收藏」系列文章目录第一章:VC++ini配置文件封装类源代码第二章:VC++实现二维码(显示+保存图片)功能源代码第三章:VC++调节系统音量(与任务栏音量同步)源代码提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录系列文章目录 前言 一、pandas是什么? 二、使用步骤 1.引入库 2.读入数据 总结前言提示:这里可以添加本文要记录的大概内容:例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启..

    2022年5月2日
    31
  • 中间人攻击原理

    中间人攻击原理中间人攻击中间人攻击 Man in the MiddleAttack 简称 MITM 攻击 是一种 间接 的入侵攻击 这种攻击模式是通过各种技术手段将受入侵者控制的一台计算机虚拟放置在网络连接中的两台通信计算机之间 这台计算机就称为 中间人 中间人攻击常见的两种方法 ARP 欺骗 DNS 欺骗 1 DNS 欺骗目标将其 DNS 请求发送到攻击者这里 然后攻击

    2026年3月18日
    2
  • Python3,选择Python自动安装第三方库,从此跟pip说拜拜!!「建议收藏」

    python安装第三方库方法1、引言2、pip手动安装2.1在线安装2.1.1pipinstall2.1.2指定版本安装2.2离线安装2.3设置国内源2.4卸载与升级2.4.1卸载2.4.2升级3、pip.main自动安装3.1pipmain安装3.2os安装4、总结1、引言续上一篇《Python3:我低调的只用一行代码,就导入Python所有库!》,小鱼发现,别说,还真有不少懒人~~不知道是不是都跟小鱼一样,把剩下的时间来学(撩)习(妹)。为了能让体现小鱼在懒上的造

    2022年4月15日
    42
  • 前缀表达式的计算机求值

    前缀表达式的计算机求值前缀表达式的计算机求值特点引例 某表达式的前缀形式为 ABCD E F GH 那么它的中缀形式为 前缀表达式的操作前缀表达式是一种没有括号的算术表达式 就是前序表达式 不同于中缀表达式 它把运算符写在前面 操作数写在后面 前缀表达式也称为 波兰式 例如 1 23 它等价于 1 2 3 后缀表达式和前缀表达式十分相似 只是后缀表达式从左往右读入计算机 前缀表达式 从右至

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号