30个精简代码的小技巧

前言

优化代码,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对代码的运行效率有什么影响呢?这个问题我是真么考虑的,好比人吃饭,吃一粒米,没用,但是一万粒,十万粒呢,这样的效率就很可观了。

代码优化的目标是:

  • 减小代码体积;
  • 提高代码运行效率。

具体方法:

1.尽量指定类,方法的final修饰符

带有final的修饰符的类是不可派生的。在java核心API中,有许多应用final的例子,例如:java.long.String,整个类都是final的。为类指定final修饰符可以让类不可被继承,为方法指定final修饰符可以让方法不被重写。如果指定了一个类为final,则该类所有的方法都是final的。 java编译器会寻找机会内联所有的final方法,内联对于提升java运行效率作用重大,大概能使性能提升50%。 内联:通常是用来消除调用函数时所需要的时间。

2.尽量复用对象

特别是String对象,出现字符串连接时应该使用StringBuffer/StringBuilder代替。由于java虚拟机不仅要花时间生成对象,以后可能还需要对这些对象进行垃圾回收和处理,因此,生成过多对象会给程序的性能带来很大影响。

3.尽可能使用局部变量

调用方法是传递的参数以及在调用中创建的临时变量都保存在栈中,相对速度比较快。其他变量,如,静态变量,实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就消失了,不需要额外的垃圾回收。

4.及时关闭流

java编程过程中,进行的数据库连接,I/O流等操作时务必当心,在使用完毕后,应及时关闭流以释放资源。因为这些大对象的操作会造成系统大的开销,会大大影响程序运行效率。

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

明确概念,对方法的调用,即使方法中只有一条语句,也是要加载的。包括创建堆栈。 调用方法时保护现场,方法结束时恢复现场等。如: for(int i = 0 ; i < list.size();i++) { } 可以替换为: for(int i = 0,length=list.size();i < length;i++) { } 这样,如果list.size()里的数据有很多时(如2000000左右),会减少很多性能消耗。

6.尽量使用懒加载策略,即在需要时才创建

如: String str = “aaa”; if(i == 1) { list.add(str); } 可以替换为: if(i == 1) { String str = “aaa”; list.add(str); }

7.慎用异常

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

8.不要在循环中使用try···catch···

应该把它放到最外层。

9.工具类指定初始长度。

如果能估计到待添加的内容长度,为底层以数组方式实现的集合,工具类指定初始长度 比如ArrayList、LinkedList、StringBuilder、StringBuffer、HashMap、HashSet等等, 以StringBuilder为例: (1) StringBuilder() //默认分配16个字符空间 (2) StringBuilder(int size) //默认分配size个字符空间 (3) StringBuilder(String str) //默认分配16个字符+str.length()个空间 可以通过类(不仅仅是StringBuilder)来设定它的初始化容量,这样可以明显提升性能。

比如,StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量加到当前的2倍在加2,无论何时,只要StringBuilder达到它的最大容量值,它就会创建一个新的字符数组然后将旧的字符数组里面的内容拷贝到新数组里是一个十分耗时的工作。

比如,一个字符数组大概要放5000个字符而不指定长度,最接近5000的2次幂是4096,那么: (1) 在4096的基础上,在申请8194+2个大小的新数组,加起来相当于共申请了12292的内存空间,如果一开始就指定长度5000或5500的话,就能节省一倍的空间。 (2) 把原来的4096个字符拷贝到新的字符数组中,这样不仅浪费内存又降低代码运行效率。

所以,给底层以数组实现的集合、工具类设置一个合理的初始值是不会有错的。 但是,注意,向HashMap这种以数组+链表实现的集合,别把初始值大小和你预估的大小设置的一样,因为一个table上连接一个对象的概率几乎为0。

建议初始大小值设为2的N此幂,如果预估是2000个元素,设置成 new HashMap(128)、new HashMap(256)都可以。

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

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

如:

for(val = 0;val < 100000;val += 5) { a = val * 8; b = val / 2; }

用移位操作可以极大的提升性能,因为在计算机底层,对位的操作是最方便的 可以替换为:

for(val = 0;val < 100000;val += 5) { a = val << 3; b = val >> 1; }

注:移位操作虽然方便,但是可能使代码不太好理解,因此需要加上相应的注释。

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

如:

for(int i = 0;i <= count; i++) { Object obj = new Object(); } 这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,可以替换为:

Object obj = null; for(int i = 0;i <= count;i++) { obj = new Object(); } 这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object而已,但是内存中只有一份,就可以节省很多内存空间了。

13. 尽可能使用array

基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList。

14. 不推荐使用Hashtable,Vector,StringBuffer,

尽量使用除非线程安全需要,否则不推荐使用Hashtable,Vector,StringBuffer,后三者由于使用同步机制而导致了性能开销。

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

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

16.尽量在何时的场合使用单例

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

  • 控制资源的使用,通过线程同步来控制资源的并发访问;
  • 控制实例的产生,达到节约资源的目的;
  • 控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。

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

因为当某个对象被定义为static时,gc通常是不会回收这个对象所占有的堆内存的, 如: public class A { private static B b = new B(); } 此时静态变量b的生命周期与A类相同,如果A类不被卸载,那么引用B指向的B对象会一直存在内存中,直到程序终止。

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

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

19. 使用for循环遍历

实现RandomAccess接口的集合比如ArrayList,应当使用for循环而不是foreach来遍历JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能够提供良好的性能。

实现RandomAccess接口类实例,加入是随机访问的,使用for循环比foreach效率高;如果不是随机访问的使用foreach效率高。 如: if(list instanceof RandomAccess) { for(int i = 0 ;i < list.size();i++){} } else { for(List li : list) { System.out.println(li); } } foreach底层实现原理就是迭代器(iterator)

20.使用同步代码块代替同步方法

除非能确定整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要同步的代码也进行同步,从而影响效率。

21.将常量声明为 STATIC FINAL

这样在编译运行时就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字进行大写的原因。

22.程序运行过程中避免使用反射

反射是java提供给用户一个很强大的功能,但是功能强大效率却不是很高。不建议在程序运行过程中频繁是哦那个反射机制,特别是Method的invoke方法。如果确实必要,建议将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存。

23.使用数据库连接池和线程池

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

24.使用带缓冲的输入,输出流进行I/O操作

带缓冲的输入,输出流即: BufferedReader,bufferedWrite,BufferedInputStream,BufferedOutputStream它们可以大大提升I/O的效率

25.合理使用 ArrayList和LinkedList

顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList理解这两个集合有何不同即可

26.不要让public方法中有太多形参

public方法是对外提供的方法,如果给这些方法太多形参的话有两点坏处: (1) 违反面相对象的思想,java讲究万物皆对象,太多形参,和java编程思想不和 (2) 参数较多会导致出错概率增加

27.字符串变量和字符串常量,equals的时候,将字符串常量写在前面

如: Strring str = “123”; if(str.equals(“123”)) { } 可以替换为: Strring str = “123”; if(“123”.equals(str)) { } 这么做是为了避免空指针的出现(中期项目有讲过)

28.不要对数组使用toString()方法

本意是想打印数组里的内容,却可能因为数组引用对象为空而导致空指针异常。虽然对数组toString()没有意义,但是对集合toString()是可以打印出集合中的内容的,因为集合的父类AbstractCollections重写了Object的toString()方法。

29.不要对超出范围的基本数据类型做向下强制转换,得到的结果绝对是错误的。

30.数据类型转为字符串,toString()最快

把一个基本数据类型转为字符串,对象点toString()是最快的方法,对象点valueOf(数据)次之,数据+””最慢,如,想把Integer i转为字符串类型,有三种方式: (1) i.toString() (2) i.valueOf(i) (3) i+””

下面测试

 public static void main(String[] args) {
      int loopTime = 50000;
      Integer i = 0;
      long startTime = System.currentTimeMillis();
      for(int j = 0 ;j < loopTime;j++) {
          String str = String.valueOf(i);
      }
                 
          System.out.println("String.valueOf():"+(System.currentTimeMillis()- startTime) +"ms");
          startTime = System.currentTimeMillis();
          for(int j = 0;j < loopTime;j++) {
              String str = i.toString();
          }
              System.out.println("Integer.toString():"+(System.currentTimeMillis()- startTime) +"ms");
                     
              startTime = System.currentTimeMillis();
              for(int j = 0 ;j < loopTime;j++) {
                  String str = i + "";
              }
                  System.out.println("i+\"\":"+(System.currentTimeMillis()- startTime) +"ms");
              }

结果:

String.valueOf():11ms;
Integer.toString():5ms;
i + "":25ms;

原理是:

(1) String.valueOf()方法调用了Integer.toString()方法,但是在调用前先做了一次空判断; (2) Integer.toString()是直接调用; (3) i + “”是使用了StringBuilder实现,先用了append方法拼接,在用toString()获取字符串

本文分享自微信公众号 - Java帮帮(javahelp)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • day08.MAPREDUCE详解【大数据教程】

    Java帮帮
  • Java面试系列13

    一、说出一些常用的类,包,接口,请各举5个 常用的类:BufferedReader BufferedWriter FileReader FileWirter ...

    Java帮帮
  • String中的null,以及String s;等区别详解

    1、判断一个引用类型数据是否null。 用==来判断。 2、释放内存,让一个非null的引用类型变量指向null。这样这个对象就不再被任何对象应用了。等待JVM...

    Java帮帮
  • Java字符串去掉空格的几种方法

    其中,\s可以匹配空格、制表符、换页符等空白字符 参考:Java正则表达式https://www.runoob.com/java/java-regular-ex...

    kirin
  • jdk1.8 自带的Base64加密与解密

  • Java14新特性:增强 instanceOf 类型推断

    obj instanceof String已经为true,在后面的代码里,我们还是要清晰的定义一个新变量,并且要做类型强转换。

    一觉睡到小时候
  • HDU - 2024 C语言合法标识符

    C语言标识符是指用来标识某个实体的一个符号,在不同的应用环境下有不同的含义,标识符由字母(A-Z,a-z)、数字(0-9)、下划线“_”组成,并且首字符不能是数...

    种花家的奋斗兔
  • PHP对Json字符串解码返回NULL的一般解决方案

    php对json字符串解码使用json_decode()函数,第一个参数传字符串,第二个参数若为true,返回array;若为false,返回object。如果...

    用户7657330
  • isEmpty 和 isBlank 区别

    org.apache.commons.lang.StringUtils 类提供了 String 的常用操作,最为常用的判空有如下两种 isEmpty(Strin...

    希希里之海
  • Java String &StringUtils

    1、首先String 是一个final类(不能被继承,可以理解为最终的,防止继承使用),里面维护了一个字节数组。 我们经常使用String 一般都是 Stri...

    邹志全

作者介绍

精选专题

活动推荐

扫码关注云+社区

领取腾讯云代金券