在java中,集合的操作 可以说是在平常不过了。对于集合可能大部分情况下都只是掌握它们的使用,其实对于它们的内部实现还是有必要了解的。这样对于学习java是一种提升。那么下面我们来学习一下ArrayList,Stack,linkedlist,hashMap四种集合框架的内部实现。
首先我们从最简单的开始ArrayList,顾名思义是数组集合,它的内部实现是基于数组的,也就是说内存空间地址是连续的,这中线性结构对于我们随机访问是非常高效的。观看源码可以发现,这个类的设计先继承一个抽象类,然后抽象类继承List接口,这里抽象类的对于就是可以先实现一些简单的方法,不想实现的可以继续交给子类来实现。但是本节重点在于怎么实现,所以我们将抽象类这个环节抛弃,直接定义接口,实现接口重而实现ArrayList。代码如下:详细注释。
package com.yxc.util;
/**
* 自己实现ArrayList集合
* 定义一个顶层接口,定义各种方法
* 我们这里使用泛型编程,方便后面的书写
* 这里我们继承Iterable接口,这样我们就可以实现遍历
*/
public interface MyList<E> extends Iterable<E>{
/**
* 在集合末尾添加一个元素
* @param elem
*/
public void add(E elem);
/**
* 在结合特定的位置添加一个元素
* @param index
* @param elem
*/
public void add(int index,E elem);
/**
* 获取指定位置的元素
* @param index
* @return E
*/
public E get(int index);
/**
* 设置指定位置的值,然后返回原来的元素
* @param index
* @param e
* @return
*/
public E set(int index,E e);
/**
* 从前往后遍历查找元素,返回下标
* @param elem
* @return index
*/
public int indexOf(E elem);
/**
* 从后往前索引元素,返回下标
* @param elem
* @return index
*/
public int lastIndexOf(E elem);
/**
* 判断集合中是不是包含某一对象
* @return
*/
public boolean Contain(E e);
/**
* 删除指定小标的元素 返回被删除元素
* @param index
* @return
*/
public E remove(int index);
/**
* 删除指定元素,返回是不是成功
* @param elem
* @return
*/
public boolean remove(E elem);
/**
* 集合的长度
* @return
*/
public int size();
/**
* 清空集合
*/
public void clear();
/**
* 判断集合是不是为空
* @return
*/
public boolean isEmpty();
/**
* 将容量和真实大小统一,只用在数组上面。
*/
public void trimToSize();
}
实现类:
package com.yxc.util;
import java.util.Iterator;
/**
* 具体的实现类,在java源码中,其实还有一个抽象类的,抽象类的作用就是先完成一些简单的实现,
* 不想实现的方法可以先不实现,这里我们为了学习简单点,直接实现所以的方法
*/
public class MyArrayList<E> implements MyList<E>{
//首先线性表是基于数据实现的,所以要定义数据的长度和初始化数组
public static final int INIT_CAPACITY=15; //默认数组的空间大小
//初始化集合的大小为0
public int size=0;
//因为我们在类上面都是用了泛型,所以这里定义数据我们可以直接使用泛型
public E elements[]=(E[])new Object[INIT_CAPACITY];
/**
* 对于数组的操作,肯定就会有容量的问题,所以肯定要有一个方法来实现扩容的操作
* 传入的参数是新的容量
*/
public void ensureCapacity(int newCapacity){
//如果我们扩容的数量小于数据的长度,那么显然扩容失败
if(elements.length>=newCapacity){
return;
}
//如果正常情况,那么就需要扩容
//扩容就是创建一个新的数组,容量比之前的大,而且要将原来数组的元素复制进去
//首先要将原来的数组保存一下
E[] oldElement=elements;
//创建一个新的数组,容量就是扩容以后的长度
E[] newElements=(E[])new Object[newCapacity];
//接着就是就是将原数组中的元素复制过来 我们可以使用for循环,但是这边我们有一个方法
System.arraycopy(oldElement,0,newElements,0,size);
//将新的数据赋值引用给elements
elements=newElements;
//释放对象,这个引用只是为了暂时保存变量,使用以后就可以释放
oldElement=null;
}
//对于新增,插入都会有一个index检查,如果越界那就是非法操作
public void checkIndex(int index){
if(index<0||index>size){
throw new IndexOutOfBoundsException("下标越界 请检查下标的合法"+index);
}
}
@Override
public void add(E elem) {
//有了下面的方法以后,那么这个方法就很好获取了
//直接在尾部插入元素
add(size,elem);
}
@Override
public void add(int index, E elem) {
//首先检查下标的合法性
checkIndex(index);
//如果容量不够,扩容 默认大小两倍
if(size==elements.length){
ensureCapacity(INIT_CAPACITY*2+1);
}
//添加一个元素,首先就是将要添加位置的元素往后移动一位,然后将要添加的元素直接复制给index位置
//可以使用循环赋值,这里还是使用数组的复制方法
System.arraycopy(elements,index,elements,index+1,size-index);
//然后将值赋值给指定位置
elements[index]=elem;
//最后长度要++
size++;
}
@Override
public E get(int index) {
checkIndex(index);
return elements[index];
}
@Override
public E set(int index, E e) {
checkIndex(index);
//用一个变量保存好index位置的数据
E temp=elements[index];
//将新元素添加进去
elements[index]=e;
//最后返回原来的数据
return temp;
}
@Override
public int indexOf(E elem) {
//循环查找元素
for(int i=0;i<elements.length;i++){
//如果找到直接返回序下标,不然返回-1
if(elem.equals(elements[i])){
return i;
}
}
return -1;
}
@Override
public int lastIndexOf(E elem) {
//从集合的最后开始遍历
for(int i=elements.length-1;i>=0;i--){
if(elem.equals(elements[i])){
return i;
}
}
return -1;
}
@Override
public boolean Contain(E e) {
int i = indexOf(e);
return i!=-1;
}
@Override
public E remove(int index) {
//删除一个元素,返回删除的元素
checkIndex(index);
//将要删除的元素保存起来
E temp=elements[index];
//然后移动数组元素,我们还是使用arraycopy函数,可以使用for循环
System.arraycopy(elements,index+1,elements,index,size-index-1);
//最后记得size--
size--;
//返回我们删除了的元素
return temp;
}
@Override
public boolean remove(E elem) {
//先将元素查找到,如果不存在,那么直接删除失败,如果存在,调用它的重载方法直接删除就可以
int index = indexOf(elem);
if(index!=-1){
remove(index);
return true;
}
return false;
}
@Override
public int size() {
return size;
}
@Override
public void clear() {
//清空集合操作就是将集合长度设置为0,然后对集合的容量重新设置
size=0;
elements=(E[])new Object[INIT_CAPACITY];
}
@Override
public boolean isEmpty() {
//判断集合长度是不是为空就可以
return size==0;
}
@Override
public void trimToSize() {
//这个方法是让集合的容量和真实大小统一
ensureCapacity(size);
}
//MyList中实现了迭代器的接口,所以这里会实现这个方法
//对于这个方法的实现我们需要创建一个内部类
//使用内部类的好处就是外部类中的变量可以直接使用
@Override
public Iterator<E> iterator() {
//需要返回一个Iterator,所以我们这里写一个内部类
return new MyIterator();
}
//内部类实现Iterator接口,实现里面的两个方法
class MyIterator implements Iterator<E>{
//当前迭代器指向0
private int curIndex=0;
@Override
public boolean hasNext() {
//是不是还有下一个元素
//只要当前下标的值小于集合的长度,那么说明还有下一个元素
return curIndex<size;
}
//返回下一个元素
@Override
public E next() {
//如果有下一个元素,那么将值返回 否则抛出异常。
if(!hasNext()){
throw new IndexOutOfBoundsException("越界了");
}
//返回以后下标要自增
return elements[curIndex++];
}
}
}
测试类:
package com.yxc.util;
import java.util.Iterator;
/**
* 测试自己手写的ArrayList是不是有效
*/
public class ArrayListTest {
public static void main(String[] args) {
MyList<Integer> myList=new MyArrayList<Integer>();
for(int i=0;i<20;i++){
myList.add(i);
}
for (int i=0;i<myList.size();i++){
System.out.println(myList.get(i));
}
//检查checkIndex的用处
// System.out.println(myList.get(10));
//下面测试一些方法
// System.out.println(myList.size());
// System.out.println(myList.isEmpty());
// System.out.println(myList.indexOf(3));
// System.out.println(myList.lastIndexOf(1));
// System.out.println(myList.Contain(3));
// Integer elem = myList.remove(3);
// System.out.println(elem);
// System.out.println(myList.size());
// myList.remove(1);
// System.out.println(myList.size());
//下面我们测试一下自己写的迭代器
Iterator<Integer> iterator = myList.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
其实如果学过数据结构,那么这一个实现是很简单的,比较困难的地方可能就是扩容以及有一个函数的使用System.arraycopy()因为参数有点多
这是一个复制数组的函数 。我们这里举个例子:假设有数组
int[] a=new int[]{1,4,5,8,2,6,7};
int[] b=new int[7];
我们现在想要把数组a中的元素复制到数组 b中,那么函数就可以这样使用
System.arraycopy(a,0,b,0,a,length)
数组a中的元素从第零号开始复制到数组b中0号开始,复制的长度是a数组的长度。