自己动手系列——实现一个简单的ArrayList
ArrayList是Java集合框架中一个经典的实现类。他比起常用的数组而言,明显的优点在于,可以随意的添加和删除元素而不需考虑数组的大小。处于练手的目的,实现一个简单的ArrayList,并且把实现的过程在此记录。
实现的ArrayList主要的功能如下:
- 默认构造器和一个参数的有参构造器
- add方法
- get方法
- indexOf方法
- contains方法
- size方法
- isEmpty方法
- remove方法
这个简单的ArrayList类 取名为SimpleArrayList
,全部的代码查看SimpleArrayList代码
构造器
源码ArrayList一共有三个构造器,一个无参构造器,一个参数为int型有参构造器,一个参数为Collection型的有参构造器。参数为Collection型的构造器用来实现将其他继承Collection类的容器类转换成ArrayList。SimpleArrayList类因为还没有手动实现其他的容器类,所以实现的构造方法只有2个。代码如下:
public SimpleArrayList(){
this(DEFAULT_CAPACITY);
}
public SimpleArrayList(int size){
if (size < 0){
throw new IllegalArgumentException("默认的大小" + size);
}else{
elementData = new Object[size];
}
}
无参构造器中的 DEFAULT_CAPACITY
是定义的私有变量,默认值是10,用来创建一个大小为10的数组。有参构造器中,int参数是用来生成一个指定大小的Object数组。将创建好的数组传给elementData
。elementData
是真正的用来存储元素的数组。
add方法
add 方法用来往容器中添加元素,add方法有两个重载方法,一个是add(E e),另一个是add(int index, E e)。add本身很简单,但是要处理动态数组,即数组大小不满足的时候,扩大数组的内存。具体的代码如下:
public void add(E e){
isCapacityEnough(size + 1);
elementData[size++] = e;
}
方法isCapacityEnough
就是来判断是否需要扩容,传入的参数就是最小的扩容空间。因为add一个元素,所以最小的扩容空间,即新的长度是所有元素+ 1。这里的size就是真正的元素个数。
private void isCapacityEnough(int size){
if (size > DEFAULT_CAPACITY){
explicitCapacity(size);
}
if (size < 0){
throw new OutOfMemoryError();
}
}
判断扩容的方法也很简单,判断需要扩容的空间是不是比默认的空间大。如果需要的空间比默认的空间大,就调用explicitCapacity
进行扩容。这里有个size小于0的判断,出现size小于0主要是因为当size超过Integer.MAX_VALUE
就会变成负数。
private final static int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
private void explicitCapacity(int capacity){
int newLength = elementData.length * 2;
if (newLength - capacity < 0){
newLength = capacity;
}
if (newLength > (MAX_ARRAY_LENGTH)){
newLength = (capacity > MAX_ARRAY_LENGTH ? Integer.MAX_VALUE : MAX_ARRAY_LENGTH);
}
elementData = Arrays.copyOf(elementData, newLength);
}
上面的代码是扩容的代码,首先,定义一个数组最大的容量的常量为最大值,这个值按照官方的源码中的解释是要有些VM保留了数组的头部信息在数组中,因此实际存放数据的大小就是整数的最大值 - 8
然后设定一个要扩容的数组的大小,虽然上面说了有一个扩容空间的值 size + 1 ,这个是实际我们最小需要扩容的大小。但为了继续增加元素,而不频繁的扩容,因此一次性的申请多一些的扩容空间。这里newLength 打算申请为 数组长度的2倍,然后去判断这个长度是否满足需要的扩容空间的值。 即有了后续的两段代码
if (newLength - capacity < 0){
newLength = capacity;
}
if (newLength > (MAX_ARRAY_LENGTH)){
newLength = (capacity > MAX_ARRAY_LENGTH ? Integer.MAX_VALUE : MAX_ARRAY_LENGTH);
}
如果2倍的长度仍然不满足,则申请到需要的扩容长度。在我们只增加一个元素的情况下,这个判断是永远不会生效的,但是如果有addAll方法,则增加的元素很多,就要导致一次申请2倍的长度是不够的。第二个判断是判断newLength的长度如果超过上面定义的数组最大长度则判断要需要的扩容空间是否大于数组最大长度,如果大于则newLength为 MAX_VALUE ,否则为 MAX_ARRAY_LENGTH。
最后,真正实现数组扩容到设定长度的方法就没意思了,调用Arrays.copyOf(elementData, newLength)
得到一个扩容后的数组。
add的另一个重载方法也很简单。
public void add(int index, E e) {
//判断是不是越界
checkRangeForAdd(index);
//判断需不需要扩容
isCapacityEnough(size + 1);
//将index的元素及以后的元素向后移一位
System.arraycopy(elementData,index,elementData,index + 1,size - index);
//将index下标的值设为e
elementData[index] = e;
size++;
}
private void checkRangeForAdd(int index){
//这里index = size是被允许的,即支持头,中间,尾部插入
if (index < 0 || index > size){
throw new IndexOutOfBoundsException("指定的index超过界限");
}
}
至此,一个简单的add方法就实现完了。
get方法
get方法用来得到容器中指定下标的元素。方法实现比较简单,直接返回数组中指定下标的元素即可。
private void checkRange(int index) {
if (index >= size || index < 0){
throw new IndexOutOfBoundsException("指定的index超过界限");
}
}
public E get(int index){
checkRange(index);
return (E)elementData[index];
}
indexOf方法
indexOf方法用来得到指定元素的下标。实现起来比较简单,需要判断传入的元素,代码如下:
public int indexOf(Object o){
if (o != null) {
for (int i = 0 ; i < size ; i++){
if (elementData[i].equals(o)){
return i;
}
}
}else {
for (int i = 0 ; i < size ; i++){
if (elementData[i] == null) {
return i;
}
}
}
return -1;
}
判断传入的元素是否为null,如果为null,则依次与null。如果不为空,则用equals
依次比较。匹配成功就返回下标,匹配失败就返回-1。
contains方法
contains用来判断该容器中是否包含指定的元素。在有了indexOf方法的基础上,contains的实现就很简单了。
public boolean contains(Object o){
return indexOf(o) >= 0;
}
size方法
size方法用来得到容器类的元素个数,实现很简单,直接返回size的大小即可。
public int size(){
return size;
}
isEmpty方法
isEmpty方法用来判断容器是否为空,判断size方法的返回值是否为0即可。
public boolean isEmpty(){
return size() == 0;
}
remove方法
remove方法是用来对容器类的元素进行删除,与add一样,remove方法也有两个重载方法,分别是
remove(Object o)和remove(int index)
public E remove(int index) {
E value = get(index);
int moveSize = size - index - 1;
if (moveSize > 0){
System.arraycopy(elementData,index + 1, elementData,index,size - index - 1);
}
elementData[--size] = null;
return value;
}
public boolean remove(Object o){
if (contains(o)){
remove(indexOf(o));
return true;
}else {
return false;
}
}
第一个remove方法是核心方法,首先得到要删除的下标元素的值,然后判断index后面的要前移的元素的个数,如果个数大于零,则调用库方法,将index后面的元素向前移一位。最后elementData[--size] = null;
缩减size大小,并将原最后一位置空。
第二个remove方法不需要向第一个方法一样,需要告诉使用者要删除的下标对应的元素,只需要判断是否删除成功即可。如果要删除的元素在列表中,则删除成功,如果不在则失败。因此调用contains
方法就可以判断是否要删除的元素在列表中。在则调用remove(int index)
,不在则返回失败。
总结
自此,一个简单的ArrayList就实现完了,实现的目的是为了弄清ArrayList动态数组的原理以及add与remove方法的内容实现。同时,也清楚了ArrayList最大的扩容空间就是Integer的最大值。该类的所有代码在SimpleArrayList代码
自己动手系列——实现一个简单的ArrayList的更多相关文章
- 自己动手系列——实现一个简单的LinkedList
LinkedList与ArrayList都是List接口的具体实现类.LinkedList与ArrayList在功能上也是大体一致,但是因为两者具体的实现方式不一致,所以在进行一些相同操作的时候,其效 ...
- java学习之—实现一个简单的ArrayList
package thread1; /** * 实现一个简单的ArrayList * * @Title: uminton */ public class SimpleArrayList<T> ...
- ROS与Matlab系列:一个简单的运动控制
ROS与Matlab系列:一个简单的运动控制 转自:http://blog.exbot.net/archives/2594 Matlab拥有强大的数据处理.可视化绘图能力以及众多成熟的算法函数,非常适 ...
- 自己动手模拟开发一个简单的Web服务器
开篇:每当我们将开发好的ASP.NET网站部署到IIS服务器中,在浏览器正常浏览页面时,可曾想过Web服务器是怎么工作的,其原理是什么?“纸上得来终觉浅,绝知此事要躬行”,于是我们自己模拟一个简单的W ...
- 扩展Python模块系列(二)----一个简单的例子
本节使用一个简单的例子引出Python C/C++ API的详细使用方法.针对的是CPython的解释器. 目标:创建一个Python内建模块test,提供一个功能函数distance, 计算空间中两 ...
- scrapy框架系列 (2) 一个简单案例
学习目标 创建一个Scrapy项目 定义提取的结构化数据(Item) 编写爬取网站的 Spider 并提取出结构化数据(Item) 编写 Item Pipelines 来存储提取到的Item(即结构化 ...
- 造轮子系列(三): 一个简单快速的html虚拟语法树(AST)解析器
前言 虚拟语法树(Abstract Syntax Tree, AST)是解释器/编译器进行语法分析的基础, 也是众多前端编译工具的基础工具, 比如webpack, postcss, less等. 对于 ...
- 手动实现一个简单的ArrayList
import org.omg.CORBA.PUBLIC_MEMBER; import java.io.Serializable; import java.util.*; import java.uti ...
- 自己动手系列----使用数组实现一个简单的Map
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同.Java 语言中提供的数组是用来存储固定大小的同类型元素. 这里提一下,数组的优缺点: 优点: 1. 使用索 ...
随机推荐
- PHP学习笔记-1——快捷键
整行删除 ctrl+E set get 生成 alt+ insert 查找.搜索和替换 Ctrl-F3 搜索位于插入点的词 F3/Shift-F3 在文件中查找下一个/上一个 Ctrl-F/H 在文件 ...
- OpenStack - liberty CentOS 7
OpenStack私有云部署 Controller Node: em1(10.6.17.11),em2() Computer Node: em1(10.6.17.12),e ...
- AWK第一篇------全面介绍
AWK-文本流编辑器 目录 [隐藏] 1 命令行语法 2 用shell实现调用awk 3 awk语言概要 3.1 记录和字段 3.2 脚本的格式 3.3 行为终止 3.4 注释 3.5 模式 3.6 ...
- 数据分析与R语言-概念点(一)
一.数据分析 1.数据分析的多层模型 常用的统计量 常用的算法 常用的数据分析工具 常见的报表 二.R语言 1.什么是R语言? R是用于统计分析.绘图的语言和操作环境.R是属于GNU系统的一个 ...
- 改变Button文字和图片的位置
button.imageEdgeInsets = UIEdgeInsetsMake(0, labelWidth, 0, -labelWidth);button.titleEdgeInsets = UI ...
- 重启机器解决SSL都要输入密码问题
在Nginx或Apache设置了SSL加密后,发现每次重启服务器后都要输入证书设置的密码,比较麻烦,不然Nginx或Apache无法使用,这时可以用私钥来做这件事.生成一个解密的key文件,替代原来k ...
- Windows Server 2012 安装sqlserver2008 小记
1.拷贝大文件被阻止 解决方案:把大文件压缩成小文件... 据说关闭防火墙会好点,没试验过. 2.安装第一步,提示没有安装.net framework 3.5 sp1 ,使用服务器管理器,添加角色 ...
- leetcode--007 word break I
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+4AAAC5CAIAAAA55fI7AAAZa0lEQVR4nO3dPW7bQIMG4L2MT6B7+A
- iOS开发UITableView基本使用方法总结 分类: ios技术 2015-04-03 17:51 68人阅读 评论(0) 收藏
本文为大家呈现了iOS开发中UITableView基本使用方法总结.首先,Controller需要实现两个delegate ,分别是UITableViewDelegate 和UITableViewDa ...
- 单纯觉得是篇好文——跨终端Web之Hybrid App
[reference]http://www.infoq.com/cn/articles/hybrid-app#theCommentsSection 编者按:InfoQ开设新栏目“品味书香”,精选技术书 ...