JAVA性能优化细节2

1.需要 Map 的主键和取值时,应该迭代 entrySet()

当循环中只需要 Map 的主键时,迭代 keySet() 是正确的。但是,当需要主键和取值时,迭代 entrySet() 才是更高效的做法,比先迭代 keySet() 后再去 get 取值性能更佳。
反例

for (String key : map.keySet()) {
  if (map.get(key).equals(arField) && !key.equals(infoId)) {
  }
}

正例

for (Map.Entry<String,String> entry : map.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
    if (value.equals(arField) && !key.equals(infoId)) {
        
    }
}

2.集合初始化尽量指定大小

java 的集合类用起来十分方便,但是看源码可知,集合也是有大小限制的。每次扩容的时间复杂度很有可能是 O(n) ,所以尽量指定可预知的集合大小,能减少集合的扩容次数。

比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例:

StringBuilder()      // 默认分配16个字符的空间
StringBuilder(int size)  // 默认分配size个字符的空间
StringBuilder(String str) // 默认分配16个字符+str.length()个字符空间

可以通过类(这里指的不仅仅是上面的StringBuilder)的构造函数来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中----这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么:

  • 在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间
  • 把原来的4096个字符拷贝到新的的字符数组中去

这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。

3.频繁调用 Collection.contains 方法请使用 Set

在 java 集合类库中,List 的 contains 方法普遍时间复杂度是 O(n) ,如果在代码中需要频繁调用 contains 方法查找数据,可以先将 list 转换成 HashSet 实现,将 O(n) 的时间复杂度降为 O(1) 。
反例

        List<String> systemFieldList = Arrays.asList(workFlowFieldArrs);
        for (Field field : fields) {
            if (!systemFieldList.contains(field.getName())) {
                continue;
            }
        }

反例

List<String> list = areaArFieldMap.get(areaKey);
if (list.contains(arField)) 
    throw new ArBillException(msg);
} else {
    list.add(arField);
}

4.长整型常量后添加大写 L

在使用长整型常量值时,后面需要添加 L ,必须是大写的 L ,不能是小写的 l ,小写 l 容易跟数字 1 混淆而造成误解。

5.不要使用魔法值

当你编写一段代码时,使用魔法值可能看起来很明确,但在调试时它们却不显得那么明确了。这就是为什么需要把魔法值定义为可读取常量的原因。但是,-1、0 和 1 不被视为魔法值。
反例

      Predicate p5 = builder.equal(root.get("isUsed"), "Y");

反例

    list = arSysDataSourceRepository.findByItemType("0");

6.删除多余代码

  • 删除未使用的私有方法和字段
  • 删除未使用的局部变量
  • 删除未使用的方法参数

7.使用 String.valueOf(value) 代替 ""+value

当要把其它对象或类型转化为字符串时,使用 String.valueOf(value) 比 ""+value 的效率更高。

      String s = "" + i;
      String s1 = String.valueOf(i);

8.过时代码添加 @Deprecated 注解

当一段代码过时,但为了兼容又无法直接删除,不希望以后有人再使用它时,可以添加 @Deprecated 注解进行标记。在文档注释中添加 @deprecated 来进行解释,并提供可替代方案

9.禁止使用构造方法 BigDecimal(double)

  • BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
        BigDecimal doubleVal = new BigDecimal(0.7);//0.6999999999999999555910790149937383830547332763671875
        BigDecimal stringVal = new BigDecimal("0.7");//0.7
  • 不要用equals方法来比较BigDecimal对象
  • BigDecimal.ZERO;
  • BigDecial是immutable的,就像String一样,它的所有操作都会生成一个新的对象,所以
    amount.add( thisAmount );
    是错误的;而应该是:
    amount = amount.add( thisAmount );

10.返回空数组和空集合而不是 null

返回 null ,需要调用方强制检测 null ,否则就会抛出空指针异常。返回空数组或空集合,有效地避免了调用方因为未检测 null 而抛出空指针异常,还可以删除调用方检测 null 的语句使代码更简洁。

11.枚举的属性字段必须是私有不可变

枚举通常被当做常量使用,如果枚举中存在公共属性字段或设置字段方法,那么这些枚举常量的属性很容易被修改。理想情况下,枚举中的属性字段是私有的,并在私有构造函数中赋值,没有对应的 Setter 方法,最好加上 final 修饰符。
反例

    /**
     * 结算方式
     */
    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    public enum SETTLEMENTTYPE {
        CASH("CASH", "现金"),
        BUSINESS("BUSINESS", "公务卡"),
        PAY("PAY", "转账"),
        CHECK("CHECK", "支票");
        private String code;
        private String describe;
    }

反例

    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    public enum TimeInfoIdEnums {
        startDate("开始日期", "START_DATE"),
        departDate("开始日期", "DEPART_DATE"),
        endDate("开始日期", "END_DATE"),
        arrivalDate("开始日期", "ARRIVAL_DATE");
        private String name;
        private String infoId;
    }

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

明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:
反例

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

正例

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

反例

if (ArStringUtil.isNotEmpty(bgItemMap.get("bgItemCur"))) {
   balance.setBgItemCur(new BigDecimal(bgItemMap.get("bgItemCur").toString()));
}

正例

Object bgItemCur = bgItemMap.get("bgItemCur");
if (ArStringUtil.isNotEmpty(bgItemCur) {
    balance.setBgItemCur(new BigDecimal(bgItemCur.toString()));
}

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

例如:

for (int i = 1; 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罢了,但是内存中只有一份,这样就大大节省了内存空间了。

14.尽量采用懒加载的策略,即在需要的时候才创建

例如:

String str = "aaa";
if (i == 1)
{
  list.add(str);
}

建议替换为:

if (i == 1)
{
  String str = "aaa";
  list.add(str);
}

14.不要创建一些不使用的对象,不要导入一些不使用的类

image.png

15.JPA将对象从持久状态转化为游离状态

        for (ArSysSetup arSysSetup : initArSysSetupList) {

            ArSysSetup newArSysSetup = new ArSysSetup();
            BeanUtils.copyProperties(arSysSetup, newArSysSetup);

            newArSysSetup.setId(null);
          
        }
        List<ArBillBalance> relationBalanceList = arBillBalanceRepository.findByBillId(RelateBillId);
        Session session = entityManager.unwrap(org.hibernate.Session.class);
        //计算对应的余额
        for (ArBillBalance arBillBalance : relationBalanceList) {
            session.evict(arBillBalance);
            BigDecimal usedMoney = arBillBalance.getUsedMoney();
            ...
            usedMoney = usedMoney.subtract(useRecord.getAmt());
            arBillBalance.setUsedMoney(usedMoney);
        }
    @PersistenceContext //注入的是实体管理器,执行持久化操作
    EntityManager entityManager;

   Session session = entityManager.unwrap(org.hibernate.Session.class);
   session.evict(obj);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,283评论 6 530
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,947评论 3 413
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,094评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,485评论 1 308
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,268评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,817评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,906评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,039评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,551评论 1 331
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,502评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,662评论 1 366
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,188评论 5 356
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,907评论 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,304评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,563评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,255评论 3 389
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,637评论 2 370