list转map的方法总结与性能优化

本文源码为jdk1.8.

楼主查阅招聘网站的各个java程序员招聘要求性能优化这个词出现的频率很高,性能优化大方向应该是针对程序的时间复杂度与空间复杂度考虑。本着知其然知其所以然的思想,总结此篇list转map。

map中的性能优化方向应该本着map中的扩容机制去优化。map初始化大小为16,负载因子0.75, 阈(yu)值 =map大小*负载因子,(map中的key对应的hash值会占用一个Node数组位置),也就是当前map中的key超过12个就会将map的大小乘以2, 已经存进去的key对应的hash值会重新计算存放。所以当前业务场景key不会有很多不同的情况下直接考虑简单点方法即可。

时间复杂度与空间复杂度 取决于集合的大小(循环的次数)与map的扩容机制执行的次数(jvm垃圾回收,扩容时是建立新的map对象,将旧map容器中的数据重新放进新的容器中,旧的容器对象并不会马上被回收)
所以 key的多样性越多时间占用空间占用越大。

List转Map的三种方法(简单业务场景)

循环取值

private static Map<String,StudyObj>  repairMapOne(List<StudyObj> studyObjs,int len){
        Map<String,StudyObj> map = new HashMap<String,StudyObj>();
        for (StudyObj thisObj:studyObjs) {
            map.put(thisObj.getStudyCode(),thisObj);
        }
        return map;
    }

相同key值的对象默认覆盖。

JDK1.8提供的stream方法(使用简单)

private static Map<String,StudyObj>  repairMapThree(List<StudyObj> studyObjs){
        Map<String, StudyObj> maps = studyObjs.stream().collect(Collectors.toMap(StudyObj::getStudyCode, Function.identity(),(key1,key2)->key1));
        return maps;
    }

参数1拿当前集合中的对象属性key值
参数2拿当前集合中的对象属性value值(Function.identity()为对象本身)
参数3为key值重复的选择 默认key值重复会抛异常

使用guava

//依赖包
<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.1-jre</version>
        </dependency>


//方法
private static Map<String,StudyObj>  repairMapTwo(List<StudyObj> studyObjs){
        Map<String, StudyObj> maps = Maps.uniqueIndex(studyObjs, new com.google.common.base.Function<StudyObj, String>() {
            @Override
            public String apply(StudyObj studyObj) {
                return studyObj.getStudyCode();
            }
        });
        return maps;
    }

// -------  源码了解

//调用的Maps工具包源码对象 map
@CanIgnoreReturnValue
        public ImmutableMap.Builder<K, V> put(K key, V value) {
            this.ensureCapacity(this.size + 1);
            Entry<K, V> entry = ImmutableMap.entryOf(key, value);
            this.entries[this.size++] = entry;
            return this;
        }
//扩容方法
private void ensureCapacity(int minCapacity) {
            if (minCapacity > this.entries.length) {
                this.entries = (Entry[])Arrays.copyOf(this.entries, com.google.common.collect.ImmutableCollection.Builder.expandedCapacity(this.entries.length, minCapacity));
                this.entriesUsed = false;
            }

        }

相同key值的对象在build时会抛异常。

感谢https://blog.csdn.net/linsongbin1/article/details/79801952

key不同值比较多的情况性能优化

知道转换原理再进行优化其实就简单许多,list转map本身都是去循环取值,所以时间复杂度都是集合的大小为界限,这个暂时还没有方法改变。但我们可以在扩容上做优化,知道其扩容限制为map容器目前存储key的数量 >(map.size()*负载因子)即扩容,那在初始化map时直接设定map大小为list的容量,负载因子设置为1.0F,不就不会扩容了吗,逻辑可行,带上实践。当然map的大小必须为16或者16的倍数,至于为什么,可以点map扩容机制,这个不在本文讨论的范围之内。map在初始化时会将传进来的initialCapacity(初始大小)计算成当前这个值最接近的16的倍数。

//源码
static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

//测试
public class StudyTwo {

    public static void main(String[] args) {
        int cap = 100;
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        System.out.println( (n < 0) ? 1 : (n >= 1000000) ? 1000000 : n + 1);
    }
//代码执行结果128
}

实践检验

实践代码中的三个方法运行时间需要保证正确性,一次只执行一个方法计算,直接三个一起执行会影响结果!!!
原因是hashcode为int类型的,在同一个jvm内存中,同一个int值只会加载一遍,后面的不会再加载而是直接引用已经加载的值。(三个方法中的list对象为同一个)
类似于

String aa="aa";
 String aaa="aa";
 // == 比较的内存对象的地址
 System.out.println(aa == aaa);
//代码执行结果
true

优化后的代码

第二种方案与第三种方案并不能自己指定map

/**
         * 100 条
         * 1611472753928
         * 1611472753928
         * 0 ~ 1 ms
         * 10W 条
         *1611469617229
         * 1611469617253
         * 24 ms
         * 100W 条
         * 1611469406040
         * 1611469407059
         * 1019 ms
         * 200W 条
         * 1611469271312
         * 1611469271804
         * 492 ms
         */
private static Map<String,StudyObj>  repairMapOne(List<StudyObj> studyObjs,int len){
        Map<String,StudyObj> map = new HashMap<String,StudyObj>(len,1.0F);
        for (StudyObj thisObj:studyObjs) {
            map.put(thisObj.getStudyCode(),thisObj);
        }
        return map;
    }

完整测试代码

/**
 * @Date 2021/1/24 上午10:10
 * @Version 1.0
 * @Remarks list 转map  根据学生编码转化map对象
 */
public class StudyOne {


    public static void main(String[] args) {
        List<StudyObj> studyObjs = initList();
        int len = studyObjs.size();
        /**
         * 100
         * 1611472753928
         * 1611472753928
         * 0 ~ 1
         * 10W
         *1611469617229
         * 1611469617253
         * 24
         * 100W
         * 1611469406040
         * 1611469407059
         * 1019
         * 200W
         * 1611469271312
         * 1611469271804
         * 492
         */
        Map<String,StudyObj>  repairMapOne = repairMapOne(studyObjs,len);
        //System.out.println(JSON.toJSONString(repairMapOne));
        /**
         * 100
         *1611472788002
         * 1611472788032
         * 30
         * 10W
         * 1611469572373
         * 1611469572440
         * 67
         * 100W
         * 1611469370395
         * 1611469371568
         * 1173
         * 200W
         * 1611469296599
         * 1611469297481
         * 882
         */
        //Map<String,StudyObj>  repairMapTwo = repairMapTwo(studyObjs);
        //System.out.println(JSON.toJSONString(repairMapTwo));
        /**
         * 100
         *1611472812247
         * 1611472812322
         * 75
         * 10W
         * 1611469515791
         * 1611469515906
         * 115
         * 100W
         * 1611469349724
         * 1611469350789
         * 1144
         * 200W
         *1611469322711
         * 1611469323283
         * 572
         */
        //Map<String,StudyObj>  repairMapThree= repairMapThree(studyObjs);
        //System.out.println(JSON.toJSONString(repairMapThree));
    }


    private static List<StudyObj> initList(){
        List<StudyObj> studyObjs = new ArrayList<StudyObj>();
        int year = 1995,yearNum,gender;
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            yearNum = random.nextInt(10);
            gender = random.nextInt(2);
            StudyObj studyObj = new StudyObj(UUID.randomUUID().toString()+i,StudyObj.StudyNameEnum.getStudyName(yearNum),(byte)gender,year+yearNum);
            studyObjs.add(studyObj);
        }
        return studyObjs;
    }

    private static Map<String,StudyObj>  repairMapOne(List<StudyObj> studyObjs,int len){
        Long startTime = System.currentTimeMillis();
        System.out.println(startTime);
        //Map<String,StudyObj> map = new HashMap<String,StudyObj>();
        Map<String,StudyObj> map = new HashMap<String,StudyObj>(len,1.0F);
        for (StudyObj thisObj:studyObjs) {
            map.put(thisObj.getStudyCode(),thisObj);
        }
        Long endTime = System.currentTimeMillis();
        System.out.println(endTime);
        System.out.println(endTime-startTime);
        return map;
    }

    private static Map<String,StudyObj>  repairMapTwo(List<StudyObj> studyObjs){
        Long startTime = System.currentTimeMillis();
        System.out.println(startTime);
        Map<String, StudyObj> maps = Maps.uniqueIndex(studyObjs, new com.google.common.base.Function<StudyObj, String>() {
            @Override
            public String apply(StudyObj studyObj) {
                return studyObj.getStudyCode();
            }
        });
        Long endTime = System.currentTimeMillis();
        System.out.println(endTime);
        System.out.println(endTime-startTime);
        return maps;
    }

    private static Map<String,StudyObj>  repairMapThree(List<StudyObj> studyObjs){
        Long startTime = System.currentTimeMillis();
        System.out.println(startTime);
        Map<String, StudyObj> maps = studyObjs.stream().collect(Collectors.toMap(StudyObj::getStudyCode, Function.identity(),(key1,key2)->key1));
        Long endTime = System.currentTimeMillis();
        System.out.println(endTime);
        System.out.println(endTime-startTime);
        return maps;
    }
}
class StudyObj {

    private String studyCode;

    private String studyName;

    private Byte studyGender;

    private Integer studyYear;


    public enum StudyNameEnum{
        XIAOMING("小明"),
        JIAWEISI("贾维斯"),
        SIRI("思睿"),
        XIAODU("小度"),
        XIAOV("小V"),
        CHENYIXUN("陈奕迅"),
        XUEZHIQIAN("薛之谦"),
        BINGBING("冰冰"),
        FANKAIJIE("樊凯杰"),
        XIAOAI("小艾"),
        ;
        private String studyName;
        public static StudyNameEnum[] STUDY_NAME_ENUMS = StudyNameEnum.values();
        public static String getStudyName(int num){
            return STUDY_NAME_ENUMS[num].getStudyName();
        }

        public String getStudyName() {
            return studyName;
        }
        StudyNameEnum(String studyName) {
            this.studyName = studyName;
        }
    }

    public StudyObj(String studyCode, String studyName, Byte studyGender, Integer studyYear) {
        this.studyCode = studyCode;
        this.studyName = studyName;
        this.studyGender = studyGender;
        this.studyYear = studyYear;
    }

    public StudyObj() {
    }

    public String getStudyCode() {
        return studyCode;
    }

    public void setStudyCode(String studyCode) {
        this.studyCode = studyCode;
    }

    public String getStudyName() {
        return studyName;
    }

    public void setStudyName(String studyName) {
        this.studyName = studyName;
    }

    public Byte getStudyGender() {
        return studyGender;
    }

    public void setStudyGender(Byte studyGender) {
        this.studyGender = studyGender;
    }

    public Integer getStudyYear() {
        return studyYear;
    }

    public void setStudyYear(Integer studyYear) {
        this.studyYear = studyYear;
    }
}

目前使用最多的应该是1.8提供的stream方法,但是效率最低。在实际使用过程中大家还是按具体业务选择转换方案比较好。
实际区别并不是很大,但是要是放在并发很大的接口上面就不是这样,扩容还会占用大量的内存空间。如果集合中有100以内的数据,一个用户节省30ms~70ms ,1K个用户,1W个用户呢。
当然16个以内的key值还是推荐使用stream方法。

以上,希望本文可以帮到你。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值