Java 代码优化

本贴最后更新于 1719 天前,其中的信息可能已经斗转星移
  1. 尽量重用对象

比如:String 对象的使用中,出现字符串连接情况时应用 StringBuffer 代替。由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理。因此,生成过多的对象将会给程序的性能带来很大的影响。

  1. 尽量使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。

  1. 及时关闭以释放资源

Java 编程过程中,进行数据库连接、I/O 流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,会导致严重的后果

  1. 对象使用完毕,应手动置成 null

JVM 回收垃圾的条件是:对象不在被引用;然而,JVM 的 GC 并非十分的机智,即使对象满足了垃圾回收的条件也不一定会被立即回收。所以,建议我们在对象使用完毕,应手动置成 null。

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

比如:

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

}

应替换为:

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

}

  1. 尽量采用 lazy loading 策略,即在需要的时候才开始创建

比如:

String str = “abc”;

if(i == 1) {

list.add(str);

}

应替换为:

if(i == 1) {

String str = “abc”;

list.add(str);

}

  1. 不要在循环中使用 try-catch,应该把其放在最外层。

  2. array(数组) 和 ArryList 的使用

array([]):最高效;但是其容量固定且无法动态改变;

ArrayList:容量可动态增长;但牺牲效率;

基于效率和类型检验,应尽可能使用 array,无法确定数组大小时才使用 ArrayList!

  1. 尽量使用 HashMap 和 ArrayList ,除非必要,否则不推荐使用 HashTable 和 Vector,后者由于使用同步机制,而导致了性能的开销

  2. 用 switch-case 替代冗长的 if-else-if

  3. 避免频繁地通过“+”运算符进行字符串拼接

原因:因为它会不断地生成新字符串对象,而生成字符串对象不仅耗时而且耗内存。应使用 StringBuilder 的 append 方法。

  1. 使用缓冲流 BufferedReader、BufferedWriter、BufferedInputStream 和 BufferedOutputStream 可以提升 IO 速度 20 倍。

  2. 尽量指定类、方法的 final 修饰符。

带有 final 修饰符的类是不可派生的。在 Java 核心 API 中,有许多应用 final 的例子,例如 java.lang.String,整个类都是 final 的。为类指定 final 修饰符可以让类不可以被继承,为方法指定 final 修饰符可以让方法不可以被重写。如果指定了一个类为 final,则该类所有的方法都是 final 的。Java 编译器会寻找机会内联所有的 final 方法,内联对于提升 Java 运行效率作用重大,具体可以查阅 Java 运行期优化相关资料,此举能够使性能平均提高 50%。

  1. 慎用异常。

异常对性能不利,抛出异常首先要创建一个新的对象,Throwable 接口的构造函数调用名为 fillInStackTrace() 的本地同步方法,fillInStackTrace() 方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java 虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

  1. 如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度。

比如 ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet 等,以 StringBuilder 为例,StringBuilder() 构造方法默认分配 16 个字符的空间,StringBuilder(int size) 构造方法默认分配 size 个字符的空间,StringBuilder(String str) 构造方法默认分配 16 个字符加 str.length() 个字符空间,所以可以通过类的构造方法来设定它的初始化容量,这样可以明显地提升性能。

  1. 当复制大量数据时,使用 System.arraycopy() 命令。

  2. 乘法和除法使用移位操作

用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,但是移位操作虽然快,可能会使代码不太好理解,因此最好加上相应的注释。

  1. 循环内不要不断创建对象引用。

  2. 不要将数组声明为 public static final

因为这毫无意义,这样只是定义了引用为 static final,数组的内容还是可以随意改变的,将数组声明为 public 更是一个安全漏洞,这意味着这个数组可以被外部类所改变。

  1. 尽量在合适的场合使用单例

使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:

控制资源的使用,通过线程同步来控制资源的并发访问;

控制实例的产生,以达到节约资源的目的;

控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信;

  1. 尽量避免随意使用静态变量

因为当某个对象被定义为 static 的变量所引用,那么 gc 通常是不会回收这个对象所占有的堆内存的。

  1. 及时清除不再需要的会话

为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为 30 分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据 MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,当会话不再需要时,应当及时调用 HttpSession 的 invalidate() 方法清除会话。

  1. 实现 RandomAccess 接口的集合(比如 ArrayList)应当使用最普通的 for 循环而不是 foreach 循环来遍历。

这是 JDK 推荐给用户的,JDK API 对于 RandomAccess 接口的解释是实现 RandomAccess 接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现 RandomAccess 接口的类实例,假如是随机访问的,使用普通 for 循环效率将高于使用 foreach 循环,反过来,如果是顺序访问的,则使用 Iterator 会效率更高。

(20)使用同步代码块替代同步方法。

尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。

(21)将常量声明为 static final,并以大写命名。

这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量。

(22)不要创建一些不使用的对象,不要导入一些不使用的类。

这毫无意义,如果代码中出现 "The value of the local variable i is not used"、"The import java.util is never used",那么请删除这些无用的内容,虽说没啥影响,但是有些时候编译期会报错,譬如没 import 用到的类被删掉了。

(23)程序运行过程中避免使用反射。

不建议在程序运行过程中使用,除非万不得已,尤其是频繁使用反射机制,特别是 Method 的 invoke 方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存,用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。

(24)使用数据库连接池和线程池。

这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程。

(25)使用带缓冲的输入输出流进行 IO 操作。

带缓冲的输入输出流,即 BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升 IO 效率。

(26)顺序插入和随机访问比较多的场景使用 ArrayList,元素删除和中间插入比较多的场景使用 LinkedList。

(27)不要让 public 方法中有太多的形参。

public 方法即对外提供的方法,如果给这些方法太多形参的话主要坏处是违反了面向对象的编程思想,Java 讲求一切都是对象,太多的形参和面向对象的编程思想并不契合,参数太多势必导致方法调用的出错概率增加。

(28)字符串变量和字符串常量 equals 的时候将字符串常量写在前面,这样可以避免空指针。

(29)建议使用 if (i == 1) 而不是 if (1 == i) 的方式。

因为有可能 == 会误写成 =,而在 C/C++ 中 if (i = 1) 是会出问题的,而 Java 会在编译时报错 "Type mismatch: cannot convert from int to boolean",但是,尽管 Java 的 if (i == 1) 和 if (1 == i) 在语义上没有任何区别,从阅读习惯上讲,建议使用前者会更好些。

(30)不要对数组使用 toString() 方法。

本意是想打印出数组内容,却打出来的是对象信息,甚至有可能因为数组引用为空而导致空指针异常。对于集合 toString() 是可以打印出集合里面的内容的,因为集合的父类 AbstractCollections 重写了 Object 的 toString() 方法。

(31)不要对超出范围的基本数据类型做向下强制转型。

这很明确,譬如 long 转 int 是会存在潜在风险的。

(32)公用的集合类中不使用的数据一定要及时 remove 掉。

如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去 remove 掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。

(33)把一个基本数据类型转为字符串,基本数据类型.toString() 是最快的方式、String.valueOf(数据) 次之、数据 +"" 最慢。

因为 String.valueOf() 方法底层调用了 Integer.toString() 方法,但是会在调用前做空判断;Integer.toString() 是直接调用;i + "" 底层使用了 StringBuilder 实现,先用 append 方法拼接,再用 toString() 方法获取字符串。

(34)使用最有效率的方式去遍历 Map。

遍历 Map 的方式有很多,通常场景下我们需要的是遍历 Map 中的 Key 和 Value,那么推荐使用的、效率最高的方式是 entrySet(),如果只是想遍历一下这个 Map 的 key 值则 keySet() 会比较合适一些。

(35)对资源的 close() 建议分开操作。

虽然有些麻烦,却能避免资源泄露,这其实和 try-catch 机制相关,各自分开 close 各自的 try-catch 就会互不影响,防止写在一个 try-catch 中因为一个异常了后面的释放不了。

(36)对于 ThreadLocal 在线程池场景使用前或者使用后一定要先 remove。

因为线程池技术做的是一个线程重用,这意味着代码运行过程中一条线程使用完毕并不会被销毁而是等待下一次的使用,而 Thread 类中持有 ThreadLocal.ThreadLocalMap 的引用,线程不销毁意味着上条线程 set 的 ThreadLocal.ThreadLocalMap 中的数据依然存在,那么在下一条线程重用这个 Thread 的时候很可能 get 到的是上条线程 set 的数据而不是自己想要的内容。这个问题非常隐晦,一旦出现这个原因导致的错误,没有相关经验或者没有扎实的基础非常难发现这个问题,因此在写代码的时候就要注意这一点,这将给你后续减少很多的工作量。

(37)切记以常量定义的方式替代魔鬼数字,魔鬼数字的存在将极大地降低代码可读性,字符串常量是否使用常量定义可以视情况而定。

(38)long 或者 Long 初始赋值时使用大写的 L 而不是小写的 l,因为字母 l 极易与数字 1 混淆,这个点非常细节,值得注意。

(39)所有重写的方法必须保留 @Override 注解。

这么做可以清楚地知道这个方法由父类继承而来,同时可以保证重写成功,此外在抽象类中对方法签名进行修改,实现类会马上报出编译错误。

(40)推荐使用 JDK7 中新引入的 Objects 工具类来进行对象的 equals 比较,直接 a.equals(b) 有空指针异常的风险。

(41)循环体内不要使用 "+" 进行字符串拼接,而直接使用 StringBuilder 不断 append。

因为每次虚拟机碰到 "+" 这个操作符对字符串进行拼接的时候会 new 出一个 StringBuilder,然后调用 append 方法,最后调用 toString() 方法转换字符串赋值给对象,所以循环多少次,就会 new 出多少个 StringBuilder() 来,这对于内存是一种浪费。

(42)不捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类。

异常处理效率低,RuntimeException 的运行时异常中绝大多数完全可以由程序员来规避,比如 ArithmeticException 可以通过判断除数是否为空来规避,NullPointerException 可以通过判断对象是否为空来规避,IndexOutOfBoundsException 可以通过判断数组/字符串长度来规避,ClassCastException 可以通过 instanceof 关键字来规避,ConcurrentModificationException 可以使用迭代器来规避。

(43)静态类、单例类、工厂类将它们的构造函数置为 private。

这是因为静态类、单例类、工厂类这种类本来我们就不需要外部将它们 new 出来,将构造函数置为 private 之后,保证了这些类不会产生实例对象

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    230 引用 • 1454 回帖 • 1 关注
  • 音乐

    你听到信仰的声音了么?

    61 引用 • 511 回帖
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • danl
    146 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 627 关注
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    76 引用 • 1737 回帖
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 4 关注
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 667 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 637 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 147 关注
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖 • 2 关注
  • 阿里巴巴

    阿里巴巴网络技术有限公司(简称:阿里巴巴集团)是以曾担任英语教师的马云为首的 18 人,于 1999 年在中国杭州创立,他们相信互联网能够创造公平的竞争环境,让小企业通过创新与科技扩展业务,并在参与国内或全球市场竞争时处于更有利的位置。

    43 引用 • 221 回帖 • 106 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 53 关注
  • OkHttp

    OkHttp 是一款 HTTP & HTTP/2 客户端库,专为 Android 和 Java 应用打造。

    16 引用 • 6 回帖 • 76 关注
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 164 关注
  • Oracle

    Oracle(甲骨文)公司,全称甲骨文股份有限公司(甲骨文软件系统有限公司),是全球最大的企业级软件公司,总部位于美国加利福尼亚州的红木滩。1989 年正式进入中国市场。2013 年,甲骨文已超越 IBM,成为继 Microsoft 后全球第二大软件公司。

    105 引用 • 127 回帖 • 370 关注
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    26 引用 • 196 回帖 • 17 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    178 引用 • 997 回帖
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖 • 44 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 672 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 363 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 76 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 536 关注
  • OnlyOffice
    4 引用 • 3 关注