在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數組的長度。