本文源码为jdk1.8.
list转map的方法总结与性能优化
楼主查阅招聘网站的各个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方法。
以上,希望本文可以帮到你。