Java:性能优化细节21-30

Java:性能优化细节21-30

21、ArrayList & LinkedList

一个是线性表,一个是链表,一句话,随机查询尽量使用ArrayList,ArrayList优于LinkedList,LinkedList还要移动指针,添加删除的操作LinkedList优于ArrayList,ArrayList还要移动数据,这两种集合在Java中有着不同的数据结构和用途,它们各自的性能优势和劣势主要由其内部实现决定。

ArrayList

  • 内部实现ArrayList基于动态数组实现,支持快速随机访问。数组使得任何位置的元素都可以在常数时间内直接访问。
  • 性能特点
    • 随机访问:访问任意元素的时间复杂度为O(1)。
    • 添加/删除元素:在列表尾部添加元素的时间复杂度也是O(1)(不考虑扩容的情况),但在列表中间或开头添加或删除元素需要移动后续的元素,时间复杂度为O(n)。

LinkedList

  • 内部实现LinkedList基于双向链表实现,每个元素都包含了前后元素的引用。
  • 性能特点
    • 随机访问:访问任意位置的元素需要从头或尾部遍历链表,时间复杂度为O(n)。
    • 添加/删除元素:在任意位置添加或删除元素时,只需要改变前后元素的引用,时间复杂度为O(1),但这是在已经有了对应节点引用的前提下;如果是索引位置操作,需要先遍历到指定位置,时间复杂度为O(n)。

应用场景

  • ArrayList:优于LinkedList当主要操作是随机访问元素。适用于元素访问频繁,添加删除操作相对较少的场景。
  • LinkedList:优于ArrayList当主要操作是在列表开头或中间添加或删除元素。适合于元素的动态插入和删除操作频繁,而访问操作相对较少的场景。

理论与实际

虽然理论上ArrayListLinkedList有明确的性能特点和适用场景,但实际应用时还需考虑其他因素,如内存使用情况、集合的大小、JVM实现细节等。例如,ArrayList的扩容操作虽然不频繁,但可能会导致较大的性能开销。另外,LinkedList虽然在理论上在某些操作上更高效,但由于其每个元素都有额外的内存开销(前后节点的引用),在大量数据操作时可能会导致较大的内存占用。

22、尽量使用System.arraycopy ()代替通过来循环复制数组

System.arraycopy() 要比通过循环来复制数组快的多,System.arraycopy()方法是Java提供的一个本地方法,用于高效地复制数组。这个方法直接利用系统级别的函数来执行数组复制,因此在性能上通常优于在Java层面用循环逐个复制数组元素的方法。

为什么System.arraycopy()更快

  1. 本地实现System.arraycopy()是一个本地方法,它直接调用底层C/C++实现,可以利用更多底层优化和指令,比如利用内存复制操作,这些通常比Java层面的代码执行得更快。

  2. 减少JVM开销:使用循环复制数组元素会产生更多的Java虚拟机(JVM)指令执行,包括循环控制、索引计算、数组访问等,而System.arraycopy()调用减少了这些开销。

  3. 减少上下文切换:在循环复制中,每次复制操作都是一个独立的Java操作,需要JVM进行解释或编译;而System.arraycopy()作为单一操作,减少了从Java代码到本地执行代码的上下文切换。

使用System.arraycopy()

public static void arraycopy(Object src,  int  srcPos,Object dest, int destPos,int length);
  • 参数说明
    • src:源数组。
    • srcPos:源数组中的起始位置。
    • dest:目标数组。
    • destPos:目标数据中的起始位置。
    • length:要复制的数组元素的数量。

示例

int[] src = new int[] {1, 2, 3, 4, 5};
int[] dest = new int[5];System.arraycopy(src, 0, dest, 0, src.length);

这个例子将src数组的全部内容复制到dest数组中。

注意事项

  • 类型兼容性srcdest数组必须具有兼容的类型。例如,你不能将一个int[]数组复制到一个long[]数组中。
  • 异常处理:如果复制过程中出现任何不兼容或不正确的参数,System.arraycopy()会抛出相应的异常,如IndexOutOfBoundsExceptionArrayStoreException

23、尽量缓存经常使用的对象

缓存是提高应用性能和响应速度的有效手段,特别是对于那些计算成本高、访问频繁或者加载时间长的资源和数据。通过避免重复的计算或数据检索,缓存可以显著减少系统的工作负载,提高效率。然而,设计和实现缓存策略时需要考虑到内存使用、缓存失效策略、数据一致性等因素。

缓存策略

  • 选择合适的数据结构:根据具体需求,可以使用简单的数据结构如HashMapArrayList或更复杂的数据结构来存储缓存数据。对于简单的缓存需求,标准的Java集合可能就足够了。
  • 缓存失效策略:合理的缓存失效策略(如FIFO、LRU、LFU等)对于维护缓存的健康状态非常重要,它可以确保缓存中存储的数据是最有可能被再次访问的,同时移除不常用的数据以释放内存。

使用第三方缓存库

对于更复杂的缓存需求,可以考虑使用成熟的第三方缓存库,它们提供了更高级的缓存功能和性能优化,以及丰富的配置选项和管理工具:

  • EhCache:是一个广泛使用的开源Java分布式缓存,为大型、高并发的Java应用提供了高性能的缓存功能。
  • Guava Cache:来自Google的Guava库中提供的缓存是一个轻量级但功能强大的本地缓存实现,适用于单个JVM内的缓存需求。
  • Caffeine:是一个高性能的Java 8缓存库,由Guava Cache的作者开发,提供了更好的性能和更丰富的功能。

缓存使用注意事项

  • 内存管理:缓存虽然可以提高性能,但也会占用额外的内存。需要根据应用的内存使用情况合理分配缓存大小,避免因缓存占用过多内存而影响应用的其他部分或导致内存溢出。
  • 数据一致性:在分布式系统中,保持缓存数据的一致性是一个挑战。需要确保缓存数据更新后,所有节点上的缓存都能同步更新,以避免数据过时。
  • 性能测试:引入缓存后应进行性能测试,确保缓存实际上提高了性能而不是因为过度缓存或不恰当的失效策略导致性能下降。

24、尽量避免非常大的内存分配

在Java中,内存分配是由堆管理器负责的。当堆中没有足够的连续空间来分配所需大小的对象时,会触发一次垃圾回收操作(GC),以尝试释放一些未使用的内存,并且尝试进行内存碎片整理。如果垃圾回收后仍然无法找到足够的连续空间,则会抛出OutOfMemoryError异常。

导致大内存分配的原因

  1. 大对象:尝试分配单个较大的对象,这会增加寻找足够连续内存的难度。例如,创建大型数组或者大型数据结构。

  2. 连续分配:连续分配多个对象,每个对象的大小都很大,这会增加找到足够连续内存的难度。例如,创建大量对象或者数据结构时,会在堆中创建很多相邻的对象。

避免大内存分配的方法

  1. 优化数据结构:尽量避免创建大型数据结构或者大型数组,可以考虑使用更小的数据结构或者分块存储数据。

  2. 释放无用资源:在不需要使用的时候及时释放不再需要的对象或者资源,以便垃圾回收能够释放相关的内存空间。

  3. 内存优化:通过分析应用的内存使用情况,优化对象的生命周期和内存使用模式,减少大内存分配的频率。

  4. 避免大量连续分配:如果需要大量的对象,可以考虑使用对象池或者缓存,避免频繁地分配和回收大量的对象,从而减少对连续内存的需求。

  5. 合理的内存配置:根据应用的内存需求和特性,合理配置Java虚拟机的内存参数,例如-Xmx和-Xms参数来调整堆内存的大小,以减少内存分配失败的可能性。

25、慎用异常

如果创建一个 Exception ,就得付出代价,好在捕获异常开销不大,因此可以使用 try-catch 将核心内容包起来。从技术上讲,你甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是 throw 操作——尽管在没有预先创建异常的情况下就抛出异常是有点不寻常。真正要花代价的是创建异常,幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则。

确实,异常处理在Java中是一个强大而又重要的机制,但过度使用异常可能会导致性能问题和代码可读性的降低。以下是关于慎用异常的一些考虑:

  1. 异常的开销:创建异常对象时会产生额外的开销,包括堆栈跟踪等信息的构建。在频繁抛出异常或者创建大量异常对象时,会对性能产生影响。

  2. 异常的语义:异常应该用于表示异常情况,而不是作为正常的控制流程。过度依赖异常来管理程序流程会使代码难以理解和维护。

  3. 异常的堆栈跟踪:异常的堆栈跟踪包含了方法调用的完整链路,它的构建可能会导致较大的性能开销。因此,在创建异常时,需要权衡是否需要完整的堆栈跟踪信息。

  4. 异常的捕获:捕获异常本身的开销相对较小,但频繁的异常捕获和处理也会影响程序的性能。因此,应该避免不必要的异常捕获,尽量将异常处理限制在必要的范围内。

  5. 异常的设计原则:异常应该用于表示不可恢复的错误或者异常情况,而不应该用于正常的控制流程。正确的异常设计可以提高代码的可读性和可维护性。

26、尽量重用对象

对象的创建和销毁会产生额外的开销,特别是在频繁创建和销毁对象的情况下。在Java中,尤其是在字符串连接等场景中,频繁创建新的字符串对象会导致性能下降,因为字符串是不可变的,每次连接字符串都会生成一个新的字符串对象。

使用StringBuffer和StringBuilder

为了避免频繁创建字符串对象,可以使用StringBufferStringBuilder来进行字符串的连接。它们是可变的字符序列,允许在不创建新对象的情况下修改字符串内容。

StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 10; i++) {buffer.append(i);
}
String result = buffer.toString();

StringBuffer是线程安全的,而StringBuilder是非线程安全的,但在单线程环境下,StringBuilder的性能会略优于StringBuffer

重用对象的原则

  1. 池化对象:可以使用对象池来重用对象,特别是对于资源消耗较大的对象,例如数据库连接、线程等。通过对象池管理对象的生命周期,可以减少对象的创建和销毁次数,提高性能。

  2. 缓存对象:对于一些频繁使用的对象,可以考虑使用缓存来重用对象,减少对象的创建次数。缓存可以是基于内存的,也可以是基于磁盘的。

  3. 避免不必要的对象创建:在编写代码时,应该避免不必要的对象创建,特别是在循环或者频繁执行的代码块中。尽量复用现有的对象,避免创建新的对象。

  4. 对象池和缓存的管理:对于使用对象池和缓存的情况,需要注意及时释放不再使用的对象,避免内存泄漏和资源浪费。

27、不要重复初始化变量

避免重复初始化变量是良好的编程实践之一。在Java中,成员变量会被自动初始化为默认值,而且构造函数链中的所有构造函数都会被调用。因此,在构造函数中重复初始化变量是不必要的,并且可能会导致混乱或错误。

最佳实践

  1. 利用默认初始化值:Java会自动为成员变量赋予默认值,包括null、0、0.0、false等。在构造函数中,不需要再次初始化这些变量,除非需要赋予不同的初始值。

  2. 合理的变量赋值时机:如果需要在构造函数中进行变量赋值,确保这些赋值操作是有必要的,并且不会与默认的初始化值冲突。

  3. 避免空指针异常:在进行变量赋值时,注意避免空指针异常。例如,在赋值时调用其他方法,确保这些方法不会依赖于尚未初始化的变量。

  4. 延迟初始化:有时候可以延迟对变量的初始化,直到真正需要使用时再进行初始化操作,以节省资源和提高性能。

  5. 统一初始化:如果有多个构造函数,可以考虑在一个初始化方法中统一初始化变量,避免在不同的构造函数中重复初始化。

示例

public class Example {private int count; // 默认初始化为0// 构造函数public Example() {// 不需要再次初始化count,已经默认初始化为0}// 初始化方法public void init() {this.count = getCountFromDatabase(); // 避免在构造函数中调用可能导致空指针异常的方法}// 延迟初始化方法public int getCount() {if (count == 0) {count = getCountFromDatabase(); // 只有在需要时才进行初始化}return count;}// 其他方法private int getCountFromDatabase() {// 从数据库获取count值return 10; // 示例,实际应用中从数据库获取}
}

28、进行数据库连接、I/O流操作,及时关闭以释放资源

及时关闭数据库连接和I/O流是Java编程中的一项重要实践,以释放资源并避免系统资源泄漏和性能下降。

数据库连接

  1. 释放资源:在使用完数据库连接后,必须调用close()方法来释放资源,避免数据库连接池的资源耗尽以及数据库服务器负载过高。

  2. 使用try-with-resources:从Java 7开始,可以使用try-with-resources语句来自动关闭资源,例如:

    try (Connection conn = DriverManager.getConnection(url, username, password);Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql)) {// 执行数据库操作
    } catch (SQLException e) {// 异常处理
    }
    

I/O流操作

  1. 关闭流:对于文件、网络等I/O流的操作,同样需要及时关闭以释放资源。对于输入流和输出流,分别调用close()方法来关闭。

  2. 使用try-with-resources:同样可以使用try-with-resources语句来自动关闭流,例如:

    try (FileInputStream fis = new FileInputStream("file.txt");InputStreamReader isr = new InputStreamReader(fis);BufferedReader br = new BufferedReader(isr)) {// 读取文件内容
    } catch (IOException e) {// 异常处理
    }
    

最佳实践

  1. 在finally块中关闭资源:如果无法使用try-with-resources,应该在finally块中手动关闭资源,以确保资源的释放。

  2. 及时释放资源:在不再需要使用资源时立即释放,避免资源泄漏和系统资源的浪费。

  3. 异常处理:在关闭资源时需要注意异常处理,确保资源能够被正确释放,同时也要处理可能出现的异常情况。

29、在使用同步机制时,应尽量使用方法同步代替代码块同步

在Java中应该尽量使用方法同步来实现同步,而不是使用代码块同步。方法同步是指使用synchronized关键字修饰整个方法,从而使得整个方法成为同步代码块;而代码块同步则是在方法内部通过synchronized关键字对一部分代码进行同步控制。

方法同步的优点:

  1. 简洁清晰:方法同步将整个方法包装在同步块中,使得代码更加简洁、清晰,不需要手动管理锁的获取和释放。

  2. 原子性:方法同步能够保证整个方法的原子性操作,避免了因为代码块同步控制不当而导致的线程安全问题。

  3. 易于维护:方法同步使得锁的范围更加明确,便于代码的维护和理解,减少了出错的可能性。

代码块同步的缺点:

  1. 容易出错:代码块同步需要手动管理锁的获取和释放,容易因为锁的粒度控制不当而导致死锁或者竞态条件等线程安全问题。

  2. 代码分散:代码块同步会导致代码的分散,需要在方法中手动标识哪些部分需要同步控制,增加了代码的复杂度和维护成本。

示例:

方法同步的示例:

public synchronized void synchronizedMethod() {// 同步代码块// ...
}

代码块同步的示例:

public void synchronizedMethod() {synchronized (this) {// 同步代码块// ...}
}

30、不要在循环中使用Try/Catch语句,应把Try/Catch放在循环最外层

在循环中使用try/catch语句可能会导致性能问题,因为每次循环迭代都会执行异常处理逻辑。因此,最好将try/catch语句放在循环的最外层,以便更有效地处理异常并提高性能。

为什么不要在循环中使用try/catch语句?

  1. 性能问题:在循环中使用try/catch语句可能会导致性能下降,因为异常处理逻辑会在每次循环迭代时执行,增加了额外的开销。

  2. 逻辑混乱:在循环中使用try/catch语句会使代码逻辑变得混乱,不利于代码的可读性和维护性。

应将try/catch放在循环最外层的原因

  1. 减少异常处理次数:将try/catch语句放在循环外部可以减少异常处理的次数,只需在循环结束后执行一次异常处理即可。

  2. 提高性能:通过减少异常处理次数,可以提高程序的性能,减少不必要的开销。

示例:

try {for (int i = 0; i < n; i++) {// 在循环中执行可能抛出异常的操作}
} catch (SomeException e) {// 处理异常
}

应改为:

try {for (int i = 0; i < n; i++) {// 在循环中执行可能抛出异常的操作}
} catch (SomeException e) {// 处理异常
}

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

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

相关文章

五、ChatGPT有哪些固有缺陷?—我耀学IT

上一讲&#xff0c;我们了解了ChatGPT工作的原理&#xff0c;这一讲&#xff0c;我们来看看它所存在的问题。 我们会看到&#xff0c;ChatGPT回答问题、写文章&#xff0c;有的时候很靠谱&#xff0c;有的时候又像在胡说八道&#xff0c;那么这些问题能不能解决呢&#xff1f; …

HTTPS对HTTP的加密过程

1、HTTPS是在HTTP的基础上&#xff0c;引入了一个加密层&#xff08;SSL&#xff09;&#xff0c;对数据进行保护&#xff0c;HTTP 是明文传输的&#xff08;不安全&#xff0c;很可能会被运营商通过referer劫持&#xff0c;或者黑客通过修改链接来窃数据&#xff09; 2、加密…

JavaSE——面向对象基础(4/4)-成员变量和局部变量的区别、面向对象综合案例(电影信息系统)

目录 补充&#xff1a;成员变量和局部变量的区别 面向对象综合案例 设计一个电影类 IDEA快捷操作 设计一个电影操作类 准备电影数据 业务处理 运行结果 补充&#xff1a;成员变量和局部变量的区别 区别成员变量&#xff08;对象的属性&#xff09;局部变量类中位置不同…

【数据结构】双向链表

一、main函数 #include <stdio.h> #include "./3.doublelinklist.h" int main(int argc, const char *argv[]) {doublelinklist* head creatr_doublelinklist();insertHead_doublelinklist(head,999);insertHead_doublelinklist(head,888);insertHead_double…

家装服务管理:Java技术的创新应用

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

SQL注入之oracle注入+SQLbypass+sqlmap实战

学习路还很长&#xff0c;切莫轻言放弃&#xff01; 目录 Oracle数据库介绍 Oracle数据库和MySQL数据库的差别 Oracle数据库注入 SQLbypass姿势 sqlmap工具实战(kali自带) Oracle数据库介绍 Oracle数据库是全球最知名的关系型数据库管理系统&#xff08;RDBMS&#xff09…

29-资源清单的管理工具-helm

一、helm的介绍 1&#xff0c;helm的价值概述 如下图所示&#xff0c;在一个企业中&#xff0c;可能存在多个不同的应用业务&#xff0c;每个业务可能包含多至十几、甚至几十个资源清单&#xff0c;那么对于“运维”和“研发”人员来讲&#xff0c;这么多的资源清单&#xff0…

说说UE5中的几种字符串类

在Unreal Engine 5 (UE5) 的C中&#xff0c;与字符串相关的类主要包括&#xff1a; FString&#xff1a; Unreal Engine中用于处理字符串的主要类&#xff0c;提供了丰富的字符串操作方法和功能。 FText&#xff1a; 用于表示本地化文本的类&#xff0c;可以包含多种语言的文本…

分布式知识整理

分布式锁 以商场系统超卖现象举例 超卖现象一 现象&#xff1a; 商品卖出数量超出了库存数量。 产生原因&#xff1a; 扣减库存的动作在程序中进行&#xff0c;在程序中计算剩余库存&#xff0c;在并发场景下&#xff0c;导致库存计算错误。 代码复现 es.shutdown(); cycl…

Nest.js权限管理系统开发(五)返回格式化

返回格式化拦截器 在上一篇《Nest.js权限管理系统开发&#xff08;四&#xff09;Swagger API接入》中&#xff0c;我们在base.controller.ts中创建了多个接口&#xff0c;每个接口都有不同的返回类型。现实中我们往往需要统一返回数据的格式&#xff0c;例如&#xff1a; {&…

【uni-app】路由

&#xff08;1&#xff09;路由配置 uni-app 页面路由全部交给框架统一管理&#xff0c;开发者需要在pages.json里配置每个路由页面的路径及页面样式&#xff08;类似小程序在 app.json 中配置页面路由&#xff09;。 "pages": [{"path": "pages/ind…

蓝桥杯倒计时47天!DFS基础——图的遍历

倒计时47天&#xff01; 深度优先搜索——DFS 温馨提示&#xff1a;学习dfs之前最好先了解一下递归的思想。 DFS基础——图的遍历 仙境诅咒 问题描述 在一片神秘的仙境中&#xff0c;有N位修仙者&#xff0c;他们各自在仙境中独立修炼&#xff0c;拥有自己独特的修炼之道…

websocket在django中的运用

14-2 聊天室实现思路&#xff1a;轮训、长轮训、websocket_哔哩哔哩_bilibili 参考大佬的B站学习笔记 https://www.cnblogs.com/wupeiqi/p/6558766.html 参考博客 https://www.cnblogs.com/wupeiqi/articles/9593858.html 参考博客 http: 是短连接&#xff0c;无状态的&…

探索网络通信的遗产:AppleTalk Data Stream Protocol (ADSP) 的全面解析

ADSP简介 AppleTalk Data Stream Protocol (ADSP) 是AppleTalk网络协议套件的一部分&#xff0c;设计用于在AppleTalk网络中提供端到端的可靠数据流服务。在1980年代和1990年代&#xff0c;AppleTalk是Apple计算机用于局域网通信的主要网络技术。ADSP提供了一种类似于现代TCP协…

conda 导出/导出配置好的虚拟环境

一. 导出环境配置&#xff08;yml文件&#xff09; 1. 在主目录下激活虚拟环境&#xff08;UE4是我的虚拟环境名称&#xff0c;请根据你自己的名称进行修改&#xff09; conda activate UE4 2. 运行此代码 conda env export > environment.yml 二. 导入环境配置&#xf…

创建第一个React项目

React脚手架 npx create-react-app react-demonpx是直接从互联网网上拉最新的脚手架进行创建react 运行React项目 npm start若想找到Webpack配置文件 npm ejectReact的基本使用 基本步骤 导入react和react-dom vue 创建react元素 渲染react元素到页面中导入 import React…

python统计分析——多解释变量的方差分析

参考资料&#xff1a;用python动手学统计学 1、导入库 # 导入库 # 用于数值计算的库 import numpy as np import pandas as pd import scipy as sp from scipy import stats # 用于绘图的库 from matplotlib import pyplot as plt import seaborn as sns sns.set() # 用于估计…

Android 10 音量UI更新解析

1 VolumeUI 的启动 由于VolumeUI 是继承 SystemUI 的&#xff0c;所以它的启动方式和 SystemUI 的启动方式一样。 直接看 VolumeUI 的start()方法 frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java Override public void start() {boolean …

Linux基础命令—进程管理

基础知识 linux进程管理 什么是进程 开发写代码->代码运行起来->进程 运行起来的程序叫做进程程序与进程区别 1.程序是一个静态的概念,主要是指令集和数据的结合,可以长期存放在操作系统中 2.进程是一个动态的概念,主要是程序的运行状态,进程存在生命周期,生命周期结…

YY调音台:直播主播的体验

我是直播平台的主播&#xff0c;日常工作就是在直播间里打游戏、唱歌、聊天之类的。刚开始的时候我的直播工具只有一台电脑&#xff0c;收音也是用的我自己常用的耳机&#xff0c;设备比较简陋&#xff0c;直播间的用户留存率也不高。但是我相信天道酬勤&#xff0c;每天晚上坚…