Java 代码优化

本贴最后更新于 1825 天前,其中的信息可能已经斗转星移
  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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3195 引用 • 8215 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 笔记

    好记性不如烂笔头。

    310 引用 • 794 回帖
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    239 引用 • 224 回帖 • 1 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    286 引用 • 248 回帖 • 10 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    172 引用 • 516 回帖
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 456 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 36 关注
  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    34 引用 • 467 回帖 • 759 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    12 引用 • 5 回帖 • 640 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    267 引用 • 666 回帖 • 3 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 164 关注
  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    410 引用 • 3588 回帖
  • Visio
    1 引用 • 2 回帖 • 2 关注
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 4 关注
  • IBM

    IBM(国际商业机器公司)或万国商业机器公司,简称 IBM(International Business Machines Corporation),总公司在纽约州阿蒙克市。1911 年托马斯·沃森创立于美国,是全球最大的信息技术和业务解决方案公司,拥有全球雇员 30 多万人,业务遍及 160 多个国家和地区。

    17 引用 • 53 回帖 • 149 关注
  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 67 回帖 • 447 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 55 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    498 引用 • 1395 回帖 • 254 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 196 关注
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖 • 2 关注
  • 创业

    你比 99% 的人都优秀么?

    82 引用 • 1395 回帖
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 297 关注
  • OpenCV
    15 引用 • 36 回帖
  • 996
    13 引用 • 200 回帖 • 4 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 9 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖 • 3 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 99 关注