[Java性能优化]_[容器创建枚举去重的最优方法]

场景

  1. 在开发Java程序时, 经常会遇到使用集合存储数据的情况。 比如集合元素去重,集合添加大量的元素,枚举集合元素。 这些常用的操作是有特定的高效率写法的,能节省时间和内存。

说明

  1. 如果明确知道元素不会超过某个数量,那么在创建集合的时候传入数值参数初始化集合的容量。这样操作能避免集合动态添加元素时频繁扩容导致时间和内存损耗。
java 复制代码
var first = new ArrayList<String>(10000);
  1. 枚举Map元素的时候使用,如果需要使用到KeyValue,那么使用Map.entrySet()方法直接返回SetKey,Value集合, 会比先返回keySet()集合再通过get(key)迭代耗费的时间少,效率更高。
java 复制代码
Set<Map.Entry<String, String>> entries = hm.entrySet();
  1. ArrayList去重,常规写法耗费时间从少到多依次是HashMap < HashSet < TreeMap
  • HashMap通过containsKey判断去重再添加进ArrayList

  • 使用HashSet传入ArrayList对象时去重。

  • TreeMap通过containsKey判断去重再添加进ArrayList

例子

java 复制代码
package test.example;

import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Consumer;

@RunWith(JUnit4.class)
public class TestCollection extends TestBase{

    private static Logger logger = Logger.getLogger(TestCollection.class);

    private final int kIterateCount = 100000;

    @Before
    public void setUp() {
        super.setUp(logger);
    }

    @After
    public void tearDown() {
        super.tearDown(logger);
    }

    protected int getListCapacity(List<?> list){
        try {
            // 获取 ArrayList 的 Class 对象
            Class<ArrayList> arrayListClass = ArrayList.class;
            // 获取名为 "elementData" 的私有字段
            Field elementDataField = arrayListClass.getDeclaredField("elementData");
            // 设置可访问私有字段
            elementDataField.setAccessible(true);
            // 获取该字段的值(即底层数组)
            Object[] elementData = (Object[]) elementDataField.get(list);
            // 数组的长度即为当前容量
            return elementData.length;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Test
    public void testInitialCapacity(){
        // 指定初始容量,提升性能;
        var first = new ArrayList<String>(10000);

        long start = System.nanoTime();
        for(int i = 0; i < 10000; ++i){
            first.add("hello");
        }
        long end = System.nanoTime();
        logger.info("first duration: " + (end-start));
        logger.info("first items size is : " + first.size());
        logger.info("first items capacity is : " + getListCapacity(first));

        var second = new ArrayList<String>();
        start = System.nanoTime();
        for(int i = 0; i < 10000; ++i){
            second.add("hello");
        }
        end = System.nanoTime();
        logger.info("second duration: " + (end-start));
        logger.info("second items2 size is :" + second.size());
        logger.info("second items2 capacity is :" + getListCapacity(second));
    }


    @Test
    public void testIteratorHashMap(){
        // HashMap因为key做了hash,查询值的时间是O(1)的复杂度。所以它的。get(key)方法很快,
        // 枚举key/value使用`keySet`不比`entrySet`慢多少。甚至比`EntrySet`更快
        HashMap<String,String> hm = new HashMap<>();
        iteratorMap(hm);
    }

    @Test
    public void testIteratorTreeMap(){
        // 使用红黑树,查询值的时间复杂度是O(log(n)).因此数据越多,查询值的时间就会增加。枚举使用`keySet`
        // 就会比`EntrySet`慢很多。
        TreeMap<String,String> hm = new TreeMap<>();
        iteratorMap(hm);
    }

    @Test
    public void testIteratorMap(){
        logger.info("===================== testIteratorHashMap =========================");
        testIteratorHashMap();

        logger.info("===================== testIteratorTreeMap =========================");
        testIteratorTreeMap();
    }

    public void iteratorMap(Map<String,String> hm){


        for(int i = 0; i< 1000000L; ++i){
            hm.put("website-"+i,"https://blog.csdn.net/infoworld");
        }

        Set<Map.Entry<String, String>> entries = hm.entrySet();

        StringBuilder sb = new StringBuilder();
        var start = startRecord();
        for(var item: entries){
            sb.append(item.getKey());
            sb.append(item.getValue());
        }
        var end = endRecord();
        pDuration(logger,start,end,"EntrySet");
//        logger.info(sb.toString());

        var keys = hm.keySet();
        sb = new StringBuilder();
        start = startRecord();
        for(var key: keys){
            sb.append(key);
            sb.append(hm.get(key));
        }
        end = endRecord();
        pDuration(logger,start,end,"KeySet");
//        logger.info(sb.toString());
    }

    @Test
    public void testRemoveDulplicationTreeMap(){

        Consumer<List<Integer>> func = lists->{
            for(int i = 0; i< kIterateCount; ++i){
                var value = (int)(Math.random()*100);
                lists.add(value);
            }
        };
        List<Integer> lists = new ArrayList<>();
        long count = 0;
        // 使用HashMap来去重
        logger.info("===================== TreeMap =========================");
        for(int i = 0; i< 1000; ++i){
            lists.clear();
            func.accept(lists);
            var map = new TreeMap<Integer,Integer>();
            var first = startRecord();
            for(var one: lists){
                if(!map.containsKey(one))
                    map.put(one,one);
            }

            lists.clear();
            var keys = map.keySet();
            for(var key : keys)
                lists.add(key);
            var second = endRecord();
            count = (count > 0)?(count + second - first)/2:(second - first);
        }

        logger.info("hashmap duration: "+ count);
//        for(var one : lists)
//            logger.info("number is: "+one);
//
//
//        for(var one : lists)
//            logger.info("number is: "+one);

    }

    @Test
    public void testRemoveDulplicationHashMap(){

        Consumer<List<Integer>> func = lists->{
            for(int i = 0; i< kIterateCount; ++i){
                var value = (int)(Math.random()*100);
                lists.add(value);
            }
        };
        List<Integer> lists = new ArrayList<>();
        long count = 0;
        // 使用HashMap来去重
        logger.info("===================== HashMap =========================");
        for(int i = 0; i< 1000; ++i){
            lists.clear();
            func.accept(lists);
            var map = new HashMap<Integer,Integer>();
            var first = startRecord();
            for(var one: lists){
                if(!map.containsKey(one))
                    map.put(one,one);
            }

            lists.clear();
            var keys = map.keySet();
            for(var key : keys)
                lists.add(key);
            var second = endRecord();
            count = (count > 0)?(count + second - first)/2:(second - first);
        }

        logger.info("hashmap duration: "+ count);
//        for(var one : lists)
//            logger.info("number is: "+one);
//
//
//        for(var one : lists)
//            logger.info("number is: "+one);

    }

    @Test
    public void testRemoveDulplicationSet(){

        Consumer<List<Integer>> func = lists->{
            for(int i = 0; i< kIterateCount; ++i){
                var value = (int)(Math.random()*100);
                lists.add(value);
            }
        };
        List<Integer> lists = new ArrayList<>();
        long count = 0;
        // 使用Set来去重
        logger.info("=================== Set ===========================");
        for(int i = 0; i< 1000; ++i){
            lists.clear();
            
            func.accept(lists);
            var sets = new HashSet<Integer>(lists);
            var first1 = startRecord();
            lists.clear();
            lists.addAll(sets);
            var second1 = endRecord();

            count = (count > 0)?(count + second1 - first1)/2:(second1 - first1);
        }

        logger.info("set duration: "+ count);
//        for(var one : lists)
//            logger.info("number is: "+one);
    }

    @Test
    public void testRemoveDulpication(){

        // 1. 使用HashSet来去重,速度并不比HashMap快,但是比TreeMap快很多。
        logger.info("Time unit is nanosecond!");
        testRemoveDulplicationHashMap();
        testRemoveDulplicationTreeMap();
        testRemoveDulplicationSet();
    }
}

输出

  1. testRemoveDulpication移除重复元素测试,元素个数有100000个,循环测试1000次求平均值。分别用HashMap,TreeMapHashSet测试速度对比。

    0 [main] INFO test.example.TestCollection - Time unit is nanosecond!
    0 [main] INFO test.example.TestCollection - ===================== HashMap =========================
    2101 [main] INFO test.example.TestCollection - hashmap duration: 278803
    2101 [main] INFO test.example.TestCollection - ===================== TreeMap =========================
    7361 [main] INFO test.example.TestCollection - hashmap duration: 3430520
    7361 [main] INFO test.example.TestCollection - =================== Set ===========================
    10182 [main] INFO test.example.TestCollection - set duration: 356707

  2. testInitialCapacity初始化10000个容量,之后添加10000个元素和不初始化容量添加10000个元素测试速度对比。

    0 [main] INFO test.example.TestCollection - first duration: 171200
    6 [main] INFO test.example.TestCollection - first items size is : 10000
    6 [main] INFO test.example.TestCollection - first items capacity is : 10000
    6 [main] INFO test.example.TestCollection - second duration: 214400
    6 [main] INFO test.example.TestCollection - second items2 size is :10000
    6 [main] INFO test.example.TestCollection - second items2 capacity is :14053

  3. testIteratorMap枚举1000000个元素,分别用entrySet获取key,value值和通过keySet先获取key再获取value测试速度对比。

    0 [main] INFO test.example.TestCollection - ===================== testIteratorHashMap =========================
    191 [main] INFO test.example.TestCollection - EntrySet duration: 58433200
    256 [main] INFO test.example.TestCollection - KeySet duration: 61964700
    256 [main] INFO test.example.TestCollection - ===================== testIteratorTreeMap =========================
    635 [main] INFO test.example.TestCollection - EntrySet duration: 85129200
    837 [main] INFO test.example.TestCollection - KeySet duration: 200515700

参考

  1. 阿里巴巴Java开发手册
相关推荐
小兔崽子去哪了1 小时前
logback 配置文件
java·后端
q***13612 小时前
史上最厉害的Java进阶之路
java·开发语言
任子菲阳2 小时前
学Java第四十五天——不可变集合、Stream流
java·开发语言·windows
q***48312 小时前
【springboot】Spring 官方抛弃了 Java 8!新idea如何创建java8项目
java·spring boot·spring
少睡点觉2 小时前
LeetCode 238. 除自身以外数组的乘积 问题分析+解析
java·算法·leetcode
952362 小时前
数据结构-二叉树
java·数据结构·学习
学IT的周星星2 小时前
SpringMVC请求参数的绑定
java·开发语言
一 乐2 小时前
宠物猫店管理|宠物店管理|基于Java+vue的宠物猫店管理管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·后端·宠物管理
r***99823 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端