Java:性能优化细节11-20

Java:性能优化细节11-20

11. 尽量合理的创建HashMap

当你要创建一个比较大的hashMap时,充分利用这个构造函数

public HashMap(int initialCapacity, float loadFactor);

合理初始化HashMap的容量和负载因子是提高HashMap性能的关键因素之一。HashMap在Java中是一种广泛使用的数据结构,用于存储键值对。正确地设置其初始容量(initialCapacity)和负载因子(loadFactor)可以显著减少哈希表重构的次数,从而优化性能。

初始容量(initialCapacity

  • 初始容量是哈希表在创建时的容量。
  • 如果预先知道将要存储的键值对数量,指定一个接近或稍微大于预计数量的初始容量可以减少或避免后续的扩容操作。
  • 扩容操作(即重新哈希)涉及到创建一个新的哈希表并重新计算每个元素的位置,这是一个相对昂贵的操作,特别是对于大型HashMap

负载因子(loadFactor

  • 负载因子是哈希表在其容量自动增加之前可以达到多满的一种度量。它是一个介于0和1之间的浮点数。
  • 默认的负载因子(0.75)提供了时间和空间成本之间的良好折衷。增加负载因子可以减少空间开销,但增加了查找成本(反映在大多数HashMap操作中,包括getput)。
  • 在设置负载因子时,需要权衡空间利用率和查询性能。如果预期HashMap中的元素会非常密集地填充,可以考虑使用稍高的负载因子;反之,如果性能是首要考虑,而空间不是问题,则可以使用较低的负载因子。

实践建议

  • 在创建HashMap时,如果有关于将要存储的元素数量的估计,应使用带有初始容量和负载因子的构造函数,以避免或减少扩容操作。
  • 对于极大或极小的数据集,可以适当调整负载因子以优化性能或空间使用率。
  • 记住,过大的初始容量可能会浪费内存,而过小的初始容量可能会导致频繁的扩容。找到合适的平衡点是关键。

通过这种方式,可以确保HashMap在性能和空间使用上都得到优化,特别是在处理大量数据时。这同样适用于HashtableVector等其他Java集合框架中的类,它们也提供了类似的构造函数来调整初始容量和负载因子。

12. 尽量减少对变量的重复计算

减少在循环中对变量或方法的重复计算是优化代码性能的一个有效手段。这种优化可以减少计算开销,特别是当循环体中的表达式涉及到复杂计算或调用成本较高的方法时。

示例优化分析

原始代码:

for(int i=0; i < list.size(); i++)

每次循环迭代都会调用list.size(),如果list.size()的调用成本较高,或者list是一个同步集合,这可能会导致性能瓶颈。

优化后的代码:

for(int i=0, len=list.size(); i < len; i++)

在循环开始之前计算了list.size()并将其存储在len变量中,这样每次循环迭代时就避免了对list.size()的重复调用。这种方式特别适用于循环次数较多的情况,可以显著减少总体的计算开销。

为什么这种优化有效

  • 减少调用开销:避免在每次迭代中重复调用方法,尤其是那些在循环执行期间不会改变返回值的方法。
  • 减少计算量:对于复杂的表达式,将其结果预先计算并存储可以避免每次迭代时的重复计算。

更多优化策略

  • 预先计算:如果循环体内有可以预先计算的表达式,应该将它们移出循环。
  • 简化表达式:简化循环条件和循环体内的表达式。例如,如果循环体内的某些计算不依赖于循环变量,可以将它们提前计算。
  • 使用增强的for循环:当遍历集合或数组时,考虑使用Java的增强for循环(for-each循环),它可以减少代码量并避免一些性能问题,尽管在某些情况下它可能不如传统for循环灵活。

13. 尽量避免不必要的创建

原始代码:

A a = new A();if(i==1){list.add(a);
}

优化建议

优化后的代码示例将对象a的创建移入了条件判断内部,这样对象a只有在条件满足时才会被创建,从而避免了不必要的对象创建:

if(i == 1) {A a = new A();list.add(a);
}

注意点

  • 作用域限制:将对象a的创建移到条件语句内部会限制a的作用域仅在条件语句内,如果后续代码需要使用a,这种方式可能会引入作用域相关的问题。

  • 条件外引用:如果需要在条件外部引用a,则需要确保a在条件外部有定义,并且在使用前已经正确初始化。例如:

    A a = null;
    if(i == 1) {a = new A();
    }
    if(a != null) {list.add(a);
    }
    

更广泛的应用

  • 懒加载:这种优化思路不仅适用于简单的条件判断,还可以应用于更复杂的场景,如对象的懒加载(按需创建)。
  • 资源管理:在处理资源密集型对象时(如文件流、数据库连接等),确保仅在需要时才创建和打开资源,使用完毕后及时关闭,可以显著降低资源消耗和提高应用性能。

14. 尽量在finally块中释放资源

程序中使用到的资源应当被释放,以避免资源泄漏,这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。

在Java中,使用资源如文件流、数据库连接或网络连接时,必须确保这些资源在使用完成后被正确关闭,以避免资源泄漏,这可能导致性能下降和资源耗尽。

使用finally块释放资源

try-catch语句中,finally块提供了一个确保执行清理工作的机制,不管是否发生异常,finally块中的代码都会执行。因此,将资源释放放在finally块中是一个好习惯,例如:

FileInputStream in = null;
try {in = new FileInputStream("file.txt");// 使用资源
} catch (IOException e) {e.printStackTrace();
} finally {if (in != null) {try {in.close(); // 确保资源被关闭} catch (IOException e) {e.printStackTrace();}}
}

使用try-with-resources语句

Java 7引入了try-with-resources语句,进一步简化了资源管理。此语法确保实现了AutoCloseable接口的资源会在语句结束时自动关闭,即使出现异常也是如此。使用try-with-resources可以使代码更简洁,减少出错的可能:

try (FileInputStream in = new FileInputStream("file.txt")) {// 使用资源
} catch (IOException e) {e.printStackTrace();
}

在这个示例中,FileInputStream资源在try块结束时会自动关闭,无需显式地在finally块中关闭它。这种方式不仅减少了代码量,还提高了代码的可读性和可维护性。

最佳实践

  • 对于Java 7及以上版本,优先使用try-with-resources语句自动管理资源。
  • 如果因为某些原因不能使用try-with-resources(如使用的Java版本较低),确保在finally块中释放资源。
  • 在释放资源时,考虑到可能的NullPointerException和其他异常,确保资源释放代码的健壮性。

15. 尽量使用移位来代替’a/b’的操作

在编程中,特别是在性能敏感的应用中,使用位移操作代替除法是一种优化性能的常见手段。位移操作在底层硬件上通常比除法操作要快得多,因为它们可以直接通过移动数字的位来实现,而不涉及复杂的算术运算。

示例说明

使用位移操作来优化除以2的幂次的操作:

  • 除以4等价于向右位移2位(a >> 2)。
  • 除以8等价于向右位移3位(a >> 3)。

注意事项

  • 适用范围:这种优化只适用于除数是2的幂次(如2、4、8、16等)的情况。对于非2的幂次的除法操作,不能直接用位移操作来替代。
  • 有符号和无符号移位:Java提供了两种右位移操作。>>是有符号右移,它会保留符号位(即负数在移位后仍然是负数),而>>>是无符号右移,不考虑符号位,空位都以0填充。根据具体情况选择适当的操作。
  • 精度问题:位移操作与除法操作在某些情况下的结果可能不完全相同,特别是涉及到负数时。位移操作是直接对二进制位进行操作,不会四舍五入或进行任何形式的调整。

注释的重要性

由于位移操作对于不熟悉二进制运算的人可能不太直观,因此,在使用位移操作时添加清晰的注释是一个好习惯。注释应解释位移操作的目的,以及它是如何等价于特定的除法操作的。例如:

// 使用位移操作来实现除以8,等价于 a / 8
int num = a >> 3;

这样做可以帮助维护代码的其他人员更容易理解代码的意图,保证代码的可读性和可维护性。

16.尽量使用移位来代替’a*b’的操作

使用位移操作来代替乘法操作,尤其是乘以2的幂次数时,确实可以提高计算效率。位移操作直接在二进制级别上移动位,比传统的乘法操作要快,因为它避免了复杂的算术运算过程。这种优化手段在性能敏感的应用中特别有价值,如图形处理、游戏开发和低级系统编程等领域。

示例说明

使用位移操作来优化乘以2的幂次的操作:

  • 乘以4等价于向左位移2位(a << 2)。
  • 乘以8等价于向左位移3位(a << 3)。

注意事项

  • 适用范围:这种优化只适用于乘数是2的幂次(2、4、8、16等)。对于其他乘数,不能使用位移操作来替代。
  • 结果的范围:需要注意的是,位移操作可能导致整数溢出,尤其是当操作的数值很大,接近其类型(如intlong)的最大值时。因此,使用位移操作时要考虑结果是否会超出预期的数值范围。
  • 有符号数:对于有符号数,左位移操作会影响符号位。在进行位移操作时,应确保不会不小心改变了数值的符号。

17. 尽量确定StringBuffer的容量

StringBuffer 的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建 StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。

如:

StringBuffer buffer = new StringBuffer(1000); 

正确初始化StringBuffer(或其后继者StringBuilder)的容量可以显著提高字符串处理的性能,尤其是在处理大量字符串拼接操作时。这是因为每次StringBuffer(或StringBuilder)容量不足以容纳更多的字符时,它都需要执行一个扩容操作,这包括分配一个新的更大的数组,复制旧数组中的内容到新数组,然后丢弃旧数组。这个过程不仅消耗时间,还会增加临时内存的使用,从而可能影响垃圾收集器的效率。

为什么指定容量是个好主意

  • 减少扩容次数:预先估计所需容量并在StringBuffer初始化时指定,可以最小化或完全避免扩容操作,从而提高性能。
  • 减少内存开销:通过避免不必要的数组复制和临时对象创建,可以减少内存使用,特别是在大量字符串操作的上下文中。

StringBuffer vs StringBuilder

虽然这里讨论的是StringBuffer,但对于单线程环境,StringBuilder通常是更好的选择,因为它不是线程安全的,因此不会因为同步操作而产生额外的性能开销。和StringBuffer一样,你也可以在创建StringBuilder实例时指定初始容量。

使用建议

  • 预估容量:在使用StringBufferStringBuilder之前,尽量预估最终字符串的长度,并据此指定一个初始容量。这可能需要根据使用场景进行一些经验估计。
  • 单线程使用StringBuilder:如果你的代码是在单线程环境下运行,考虑使用StringBuilder代替StringBuffer,因为前者在没有线程安全需求时提供了更好的性能。
  • 动态估计:在某些情况下,如果你事先不知道需要多少容量,可以考虑根据动态数据来估计容量。例如,如果你在循环中添加字符串,可以根据循环次数和平均字符串大小来计算一个大致的容量需求。

18. 尽量早释放无用对象的引用

合理管理对象引用对于优化Java程序的内存使用和性能非常重要。虽然Java虚拟机(JVM)的垃圾收集器(GC)会自动回收无用对象占用的内存,但是合理地控制对象的生命周期可以更早地释放内存,特别是在处理大对象或内存敏感的应用中。

局部变量的引用释放

对于方法中的局部变量,一旦变量超出了其作用域,就会自然地失去引用,其指向的对象也就变成了垃圾回收的候选对象。因此,通常情况下,无需显式地将局部引用变量设置为null。下面的做法是多余的:

public void test() {Object obj = new Object();// ...其他操作...obj = null; // 没有必要
}

长生命周期的对象引用释放

然而,在某些情况下,尤其是在对象的引用在长时间执行的方法中仍然保持活跃,但实际上已经不再需要时,显式地释放这些引用(即将引用设置为null)可能有助于垃圾收集器更早地回收这些对象,从而减少内存占用,尤其是在如下情况:

public void test() {Object obj = new Object();// ...其他操作...obj = null; // 在执行耗时或耗内存的操作前显式释放引用// 执行耗时,耗内存操作;或调用耗时,耗内存的方法
}

最佳实践

  • 对于短生命周期的局部变量,无需显式设为null:方法执行完毕后,局部变量会自动失去作用域,因此它们引用的对象会自然成为垃圾回收的候选。
  • 在长时间运行的方法中及时释放不再需要的对象引用:如果方法执行时间很长,且某些对象在方法中早期就已经不再需要,那么将这些对象引用设为null可以帮助垃圾收集器更早回收这些对象。
  • 谨慎使用null:虽然在某些情况下设为null有助于内存管理,但过度使用可能会使代码难以理解和维护,甚至引入空指针异常的风险。因此,仅在必要时才这样做,并确保这种做法的目的和理由是清晰的。

19. 尽量避免使用二维数组

在Java中,数组是存储在堆内存上的对象。一个二维数组,从本质上讲,是一个数组的数组,即外层数组的每个元素都是另一个数组的引用。这种结构确实比一维数组使用更多的内存,主要原因包括:

  1. 引用开销:在二维数组中,外层数组包含了对内层数组的引用。每个引用本身就是一个额外的内存开销。
  2. 对象头开销:Java中的每个对象都有一个对象头(包含类指针、垃圾收集信息等),因此每个内层数组都会有这样的开销。
  3. 内存对齐:Java虚拟机可能会对对象数据进行内存对齐,增加额外的填充以优化性能,这也可能导致额外的内存使用。

使用场景和替代方案

  • 使用场景:在需要表达多维数据结构时,二维数组是一种直观的选择。例如,在处理矩阵、网格或任何需要行和列结构的数据时。
  • 性能考虑:如果性能和内存效率是首要考虑的话,可以考虑其他数据结构作为替代。例如,可以使用一维数组来模拟二维数组,通过计算索引来访问元素,这样可以减少内存的开销,尤其是在处理非常大的数组时。
  • 替代方案:对于复杂的数据结构,还可以考虑使用集合类(如ArrayList),或者创建自定义类来更有效地管理数据和内存。

20. 尽量避免使用split

避免使用split方法的建议主要基于性能考虑。String.split()方法在Java中确实是基于正则表达式来分割字符串的,这意味着即使是简单的分割操作也涉及到正则表达式的编译和匹配过程,这在处理大量数据时可能会成为性能瓶颈。

性能影响

  • 正则表达式编译split方法内部使用正则表达式,每次调用时都需要编译正则表达式,这是一个相对昂贵的操作。
  • 复杂度:正则表达式的复杂度可能会对性能产生显著影响,特别是当表达式复杂或输入字符串较长时。

替代方案

  • 使用StringUtils.split:Apache Commons Lang库提供的StringUtils.split方法是一个不基于正则表达式的替代方案,它对于简单的分隔符分割操作来说,性能通常优于String.split()

  • 手动实现分割逻辑:对于非常特定的分割需求,手动实现分割逻辑(如使用循环和String.substring())可能会提供更好的性能。这种方法允许细粒度控制和优化,但可能会增加实现的复杂性。

  • 结果缓存:如果对相同的字符串执行相同的分割操作多次,考虑将结果缓存起来重用,以避免重复的分割操作。

使用建议

  • 需求分析:在选择使用split还是其他方法之前,应该根据实际需求进行分析。如果分割操作简单且不频繁,使用split可能是可接受的。对于高性能需求,考虑使用更高效的方法。
  • 性能测试:在决定最终实现时,进行性能测试比理论分析更为重要。不同的方法在不同的上下文和数据集上可能表现不同,实际测试可以帮助确定最合适的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/700844.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux--shell编程中分区表常用操作 全面且详细

文章中关于分区表常用操作目录&#xff1a; 一、概念 二、​​​​​​​创建分区表语法 ​​​​​​​三、创建一个表带多个分区 四、​​​​​​​加载数据到分区表中 五、加载数据到一个多分区的表中去 ​​​​​​​六、查看分区 七、​​​​​​​添加一个分区…

ChatGPT大致运用了哪些技术

ChatGPT是一个基于OpenAI的GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构构建的语言模型。它的大致原理和运用的技术如下&#xff1a; Transformer架构&#xff1a;ChatGPT基于Transformer模型&#xff0c;这是一种使用自注意力机制&#xff08;self-att…

机器学习:逻辑回归原理

逻辑回归模型是一种广泛应用于分类问题的统计方法。尽管名为“回归”&#xff0c;但它实际上是一种分类算法&#xff0c;主要用于预测观察对象属于某个类别的概率。逻辑回归模型特别适用于二分类问题&#xff0c;但也可以通过一些策略扩展到多分类问题。 逻辑回归的应用与优化…

让C语言代码变抽象(二)

目录 前言&#xff1a; 代码&#xff1a; 前言&#xff1a; 在今天写代码的时候&#xff0c;我又想到一个更抽象的代码。 我在写注释的时候突然想想到条件编译的东西&#xff0c;好像也能用来注释东西。 代码&#xff1a; 我们在这直接上干货 我们知道在条件编译中有一个叫…

飞常准查航班小程序采集

仅限学习使用 <html> <head> </head> <body><script src"AesUtil.js"></script><script src"md5.js"></script><script>function test(a) { return true; }function serialize(o) {var n argumen…

【前端素材】推荐优质后台管理系统Acara平台模板(附源码)

一、需求分析 后台管理系统在多个层次上提供了丰富的功能和细致的管理手段&#xff0c;帮助管理员轻松管理和控制系统的各个方面。其灵活性和可扩展性使得后台管理系统成为各种网站、应用程序和系统不可或缺的管理工具。 后台管理系统是一种用于管理和控制网站、应用程序或系…

Map集合特点、遍历方式、TreeMap排序及Collections和Arrays

目录 ​编辑 一、集合框架 二、 Map集合 特点 遍历方式 HashMap与Hashtable的区别 TreeMap Collections Arrays 一、集合框架 二、 Map集合 Map集合是一种键值对的集合&#xff0c;其中每个键对应一个值。在Java中&#xff0c;Map接口定义了一种将键映射到值的数据结…

Python中websockets服务端从客户端接收消息并发送给多线程

思路&#xff1a; 1.websockets需要从客户端接收消息&#xff0c;由于websockets创建服务端只能绑定一个端口&#xff0c;所以需要单独占用一个线程。收到的消息&#xff0c;我们需要共享给主线程&#xff0c;然后主线程根据设备&#xff08;多线程&#xff09;分发消息给各线…

嵌入式学习-qt-Day3

嵌入式学习-qt-Day3 一、思维导图 二、作业 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳…

「Java开发指南」MyEclipse如何支持Spring Scaffolding?(三)

在上文中&#xff08;点击这里回顾>>&#xff09;&#xff0c;主要为大家介绍了CRUD Scaffolding&#xff0c;本文将继续介绍应用程序的分层、代码助手等。 MyEclipse v2023.1.2离线版下载 3. 应用程序的分层 应用程序分层是应用程序开发领域中非常常见的体系结构方法…

C++之类作用域

目录 1、全局作用域 2、类作用域 2.1、设计模式之Pimpl 2.2、单例模式的自动释放 2.2.0、检测内存泄漏的工具valgrind 2.2.1、可以使用友元形式进行设计 2.2.2、内部类加静态数据成员形式 2.2.3、atexit方式进行 2.2.4、pthread_once形式 作用域可以分为类作用域、类名…

c++学习记录 STL基本概念

1、STL基本概念 STL&#xff08;Standard Template Library&#xff0c;标准模板库&#xff09;STL广义上分为&#xff1a;容器&#xff08;container&#xff09;算法&#xff08;algorithm&#xff09;迭代器&#xff08;iterator&#xff09;容器和算法之间通过迭代器进行无…

线程共享和非共享的资源及线程优缺点

注意&#xff1a;共享的内存地址空间中不包括栈&#xff1b;共享文件描述符表&#xff0c;表示&#xff0c;同一进程中线程可以操作同一文件。

猫头虎分享已解决Bug || TypeError: Cannot read property ‘props‘ of undefined (React)

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

c++的类型转换方法

一、静态类型转换&#xff08;static_cast&#xff09; 静态类型的转换主要用于基本类型之间的转换&#xff0c;比如int类型转换为double类型。但是static_cast也可以支持上下行的转换&#xff08;存在继承关系之间的转换&#xff09; 基本类型之间的转换举例 上下行转换的举…

金航标电子位于广西柳州鹿寨县天线生产基地于大年正月初九开工了!!!

金航标kinghelm&#xff08;www.kinghelm.com.cn&#xff09;总部位于中国深圳市&#xff0c;兼顾技术、成本、管理、效率和可持续发展。东莞塘厦实验室全电波暗室、网络分析仪、高低温测试柜等仪器设备齐全&#xff0c;可进行高低温、双85等测试&#xff0c;独立完成产品的检测…

Linux内核中并发与竞争的处理方法:原子操作代码举例二

一. 简介 前面文章学习了Linux内核中处理并发与竞争的一种方法&#xff1a;原子操作&#xff0c;并编写代码说明原子操作中对整型变量的操作&#xff0c;文章地址如下&#xff1a; Linux内核中并发与竞争的处理方法&#xff1a;原子操作代码举例一-CSDN博客 本文学习原子操…

流畅的Python(十二)-继承的优缺点

一、核心要义 1. 子类化内置类型的缺点 2.多重继承和方法解析顺序 二、代码示例 1. 子类化内置类型的缺点 #!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2024/2/24 7:29 # Author : Maple # File : 01-子类化内置类型的问题.py # Software: PyCharm fr…

数字化转型导师坚鹏:数据安全法解读与政府数字化转型

网络安全法、数据安全法、个人信息保护法解读与政府数字化转型 课程背景&#xff1a; 很多机构存在以下问题&#xff1a; 不清楚网络安全法、数据安全法、个人信息保护法立法背景&#xff1f; 不知道如何理解网络安全法、数据安全法、个人信息保护法政策&#xff1f; 不…

高级RAG:使用RAGAs + LlamaIndex进行RAG评估,包括原理、图和代码

原文地址&#xff1a;Using RAGAs LlamaIndex for RAG evaluation 2024 年 2 月 5 日 如果您已经为实际的业务系统开发了检索增强生成&#xff08;Retrieval Augmented Generation, RAG&#xff09;应用程序&#xff0c;那么您可能会关心它的有效性。换句话说&#xff0c;您…