验证ArrayList插入同样数据使用指定容量和默认容量的效率
2019独角兽企业重金招聘Python工程师标准>>>
验证ArrayList插入同样数据使用指定容量和默认容量的效率
之前在研究ArrayList源码的时候看到过一篇文章Java 8 容器源码-ArrayList里面说当ArrayList在进行插入的时候,如果容量不够那么就会进行自动扩容,扩容大小是现有容量的1.5倍,具体代码可以参考下面。
此处的默认容量是指当构建空的ArrayList构造函数时给分配的默认数组容量大小,为10。
/*** 扩容,保证ArrayList至少能存储minCapacity个元素* 第一次扩容,逻辑为newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基础上增加一半。第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。** @param minCapacity 想要的最小容量*/private void grow(int minCapacity) {// 获取当前数组的容量int oldCapacity = elementData.length;// 扩容。新的容量=当前容量+当前容量/2.即将当前容量增加一半。int newCapacity = oldCapacity + (oldCapacity >> 1);//如果扩容后的容量还是小于想要的最小容量if (newCapacity - minCapacity < 0)//将扩容后的容量再次扩容为想要的最小容量newCapacity = minCapacity;//如果扩容后的容量大于临界值,则进行大容量分配if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData,newCapacity);}/**
所以在网上就会有许多的人说当知道了要用ArrayList存储多少数据的前提下,我们要指定ArrayList的容量大小,这样会减少ArrayList自动扩容的次数而增加效率。
此时相信大家心里就会和我有一样的疑问,在不同数据量的面前使用默认容量和指定容量大小效率到底会有多少的差别。相信有些人(比如我)一开始上来就会写如下的代码进行验证。
public class TestListAdd {public static void main(String[] args){System.out.println("Test 10000000 List add start");System.out.println("Default Capacity: "+ listAdd(10000000));//默认容量System.out.println("10000000 Capacity: "+ listAdd(10000000,10000000));//指定容量}public static Long listAdd(int num){Long starTime = System.currentTimeMillis();List<Object> list = new ArrayList<>();for (Integer i = 0; i < num; i++) {list.add(i);}Long endTime = System.currentTimeMillis();return endTime - starTime;}public static Long listAdd(int capatity,int num){Long starTime = System.currentTimeMillis();List<Object> list = new ArrayList<>(capatity);for (Integer i = 0; i < 10000000; i++) {list.add(i);}Long endTime = System.currentTimeMillis();return endTime - starTime;}
}
结果当然也是符合预期的,下面的输出比上面的要少许多
Test 10000000 List add start
Default Capacity: 2416
10000000 Capacity: 397
如果试验到此为止我也就不会写这篇博客了,因为没什么可写的了,但是当我将上面默认容量大小和指定容量大小两行代码换个位置,那么结果就会与我们所想的不相同。
Test 10000000 List add start
10000000 Capacity: 2130
Default Capacity: 933
经过查询各种资料,中间走了许多的坑,例如一开始我想着是第一次的调用会将int从0-10000000的数第一次创建到常量池里面,而第二次就不用创建直接去常量池里面去取第一次创建好的int类型就好了,所以每次的第一次调用会比第二次调用速度要慢许多。当然随后经过学习查资料,否定了我第一次的蠢想法。两个不同的方法内的变量根本不会有交互。随后看到了一篇文章和我的问题差不多java循环长度的相同、循环体代码相同的两次for循环的执行时间相差了100倍?里面的回答提到了一句话。
在HotSpot VM上跑microbenchmark切记不要在main()里跑循环计时就完事。这是典型错误。重要的事情重复三遍:请用JMH,请用JMH,请用JMH。除非非常了解HotSpot的实现细节,在main里这样跑循环计时得到的结果其实对一般程序员来说根本没有任何意义,因为无法解释。
当然里面的其他术语我是看不懂的,只是简单的明白了两点
- 使用main方法进行微测一个方法的执行效率是不对的。具体怎么不对,我也不清楚,看不懂,里面的答案有说明。
- 要使用JMH、要使用JMH、要使用JMH。
JMH 是 Java Microbenchmark Harness(微基准测试)框架的缩写(2013年首次发布)。与其他众多测试框架相比,其特色优势在于它是由 Oracle 实现 JIT 的相同人员开发的。
我会在后面贴出一些学习JMH的经典文章。下面就开始我们的测试吧。
测试准备
使用JMH进行测试十分的简单,只需要加入一些注解即可。他就能屏蔽一些JVM对于测试的影响。让我们只关注于测试结果。
@Benchmarkpublic static void listAdd(Blackhole blackhole){List<Object> list = new ArrayList<>(10000);for (Integer i = 0; i < 10000; i++) {list.add(i);}blackhole.consume(list);}
我的JMH的配置如下
- warmup iterations的数量: 10
- iterations的数量:10
- forks的数量:2
- threads的数量:1
- 时间:TimeUnit.NANOSECONDS
- Mode:Mode.AverageTime
具体的测试代码我就不展示了,其中无非就是循环次数和初始化的容量。我就直接将数据展示出来吧。
循环次数 | 初始化容量 | 平均时间(微秒) |
---|---|---|
10 | 10 | 55.38 |
100 | 79.06 | |
1000 | 361.85 | |
10000 | 2355.82 |
循环次数 | 初始化容量 | 平均时间(微秒) |
---|---|---|
100 | 10 | 524.50 |
100 | 506.01 | |
1000 | 851.23 | |
10000 | 2804.22 |
循环次数 | 初始化容量 | 平均时间(微秒) |
---|---|---|
1000 | 10 | 7233.56 |
100 | 6923.32 | |
1000 | 5284.91 | |
10000 | 7723.32 |
循环次数 | 初始化容量 | 平均时间(微秒) |
---|---|---|
10000 | 10 | 81188.92 |
100 | 64393.20 | |
1000 | 59316.76 | |
10000 | 51382.20 |
下面是根据上面数据画的折线图,能够更加直观的感受到变化。
总结
大概由上面的试验,我们能够得出以下结论
- 插入总数一定时,当初始化容量<插入总数。那么初始化容量越大效率越高。
- 插入总数一定时,当初始化容量>插入总数。那么初始化容量越大效率越低。
尤其这种效率的提升在数据量大的时候更为明显,因为数据量大而导致初始化容量不够,扩容次数不断的增加。导致效率降低。
参考文章
- JAVA拾遗 — JMH与8个测试陷阱
- Java 并发编程笔记:JMH 性能测试框架
- Java微基准测试框架JMH
- JMH使用说明
- Java 8 容器源码-ArrayList
- https://dzone.com/articles/performance-evaluation-of-java-arraylist
转载于:https://my.oschina.net/u/4030990/blog/3018722
验证ArrayList插入同样数据使用指定容量和默认容量的效率相关推荐
- 验证DetailsView插入数据不为空
验证DetailsView插入数据不为空,在对象数据源ObjectDataScource(ChannelDS)的Inserting事件中写如下代码: protected void Channe ...
- FreeSql (八)插入数据时指定列
插入数据时指定列,和忽略列对应,未被指定的列将被忽略. var connstr = "Data Source=127.0.0.1;Port=3306;User ID=root;Passwor ...
- 2017.4.16 ArrayList初始默认容量(长度)
版权声明:本文原创作者:一叶飘舟 作者博客地址:http://blog.csdn.net/jdsjlzx 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.它总是至少等于 ...
- Java导出Excel模板,导出数据到指定模板,通过模板导入数据(一)
本文章主要是介绍阿里巴巴的easyexcel的使用 1. 首先需要我们导入easyexcel的依赖包 <!-- alibaba/easyexcel 使用高版本,低版本string接收数字丢小数位 ...
- 在列表前方插入一个数据_通俗易懂的Redis数据结构基础教程
Redis有5个基本数据结构,string.list.hash.set和zset.它们是日常开发中使用频率非常高应用最为广泛的数据结构,把这5个数据结构都吃透了,你就掌握了Redis应用知识的一半了. ...
- java POI对word中的表格动态插入固定数据,以及插入不确定数量的的数据
java POI对word中的表格动态插入固定数据,以及插入不具体的数据 遇到个项目本来是用Execl导出的,相对简单,客户要求用Word导出,并按照他们给的模板进行导出: 从网上百度了一下,然后自己 ...
- 技术图文:如何利用 C# 向 Access 数据库插入大量数据?
背景 通常我们在做数据分析与处理之前,需要把从网站爬取的数据或者从 甲方 系统中导出的数据存入到自己的数据库中.如果数据量小,直接利用 SQL的 Insert 语句逐条插入就好.可是数据量上万条之后, ...
- 在SQL 2005中用T-SQL插入中文数据时出现的问号或乱码的解决方案[转]
在SQL 2005中用T-SQL插入中文数据时出现的问号或乱码的解决方案 病症表现为:主要表现为用T-sql语句插入中文数据时数据库显示全是问号"???" 解决办法: 第一种办法 ...
- mysql插入大量数据总结
1 java实现 package cn.edu.nwsuaf.sheep2.utils;import java.sql.Connection; import java.sql.DriverManage ...
最新文章
- 深入理解计算机系统(3.4)------算术和逻辑操作
- Spring源码由浅入深系列一 简介
- 玩转GIT系列之【如何放弃本地/服务器端所做的修改】
- 爬虫:验证码识别准确率(Tesseract-OCR)
- 《团队激励与沟通》第 8 讲——团队合作技巧 重点部分总结
- 如何得到所有子对象_对象可能会迟到,但它永远不会缺席
- HTML的基本知识-和常用标签-以及相对路径和绝对路径的区别
- Linux突然无法使用,是内存不足的问题
- Qt 流畅的运行大循环
- 《王阳明心学及其当代意义》观后总结
- 开源软件管理调研报告
- 创宇区块链|重蹈覆辙?为何 DEUS 协议再受攻击?
- 在Java中实现有账号密码的Http代理访问
- 操作系统进程线程区别、并发和并行、内存和外存
- longest-common-prefix[最长公共子序列]
- 【Linux】进程通信
- 推荐系统3--FM和FFM
- Halcon标定板标定
- 大众中国纯电动战略“水土不服”?理想ONE冲击月销过万目标
- win10 移动硬盘无法弹出 提示设备已被占用