基于数组、链表、哈希表实现自定义的List、Map接口

一、基于数组实现自定义的List接口

1.数组介绍
  • 数组长度固定,不允许动态定义数组大小,在使用之前必须确定大小;
  • 存储数据类型相同;
  • 各个元素存储是有先后顺序的,在内存中按照一定先后顺序连续存放在一起;
2.简单代码实现
/*自定义List接口*/
public interface MyList<E> {
    int size();

    boolean isEmpty();

    boolean contains(E e);

    void clear();

    void add(E e);

    void add(int index, E e);

    boolean remove(int index);

    boolean set(int index, E e);

    E get(int index);
}
/*基于数组实现自定义List接口*/
public class MyArrayList<E> implements MyList<E>, Iterable<E> {
    private static final int DEFAULT_CAPACITY = 10;

    private int capacity;

    private int size;

    private Object[] arrayList;

    public MyArrayList() {
        this.capacity = DEFAULT_CAPACITY;
        initArrayList();
    }

    public MyArrayList(int capacity) {
        this.capacity = capacity;
        initArrayList();
    }

    private void initArrayList() {
        this.size = 0;
        this.arrayList = new Object[this.capacity];
    }

     @Override
    public int size() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return this.size == 0;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean contains(E e) {
        if (e == null) {
            return false;
        }
        for (Object o : arrayList) {
            E element = (E) o;
            if (e.equals(element)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void clear() {
        this.capacity = DEFAULT_CAPACITY;
        initArrayList();
    }

    @Override
    public void add(E e) {
        add(size(), e);
    }

    @Override
    public void add(int index, E e) {
        if (size >= capacity) {
            this.capacity *= 2;
            Object[] newArrayList = new Object[capacity];
            System.arraycopy(this.arrayList, 0, newArrayList, 0, size);
            this.arrayList = newArrayList;
        }
        if (size - index >= 0) {
            System.arraycopy(this.arrayList, index, this.arrayList, index + 1, size - index);
        }
        this.arrayList[index] = e;
        size++;
    }

    @Override
    public boolean remove(int index) {
        if (index < 0 || index >= this.size) {
            throw new IndexOutOfBoundsException();
        } else {
            if (size - 1 - index >= 0) {
                System.arraycopy(arrayList, index + 1, arrayList, index, size - 1 - index);
            }
            size--;
            return true;
        }
    }

    @Override
    public boolean set(int index, E e) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException();
        } else {
            arrayList[index] = e;
            return true;
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException();
        } else {
            return (E) arrayList[index];
        }
    }

    @Override
    public Iterator<E> iterator() {
        return new ArrayListIterator();
    }

    @SuppressWarnings("unchecked")
    private class ArrayListIterator implements Iterator<E> {
        private int current = 0;

        @Override
        public boolean hasNext() {
            return current < size();
        }

        @Override
        public E next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return (E) (arrayList[current++]);
        }

        @Override
        public void remove() {
            MyArrayList.this.remove(--current);
        }
    }
}
/*自定义ArrayList 测试类*/
public class MyArrayListTest {
    @Test
    public void should_return_size_of_array_list() {
        MyArrayList<String> arrayList = new MyArrayList<>();
        assertThat(arrayList.size()).isEqualTo(0);
        arrayList.add("hello");
        assertThat(arrayList.size()).isEqualTo(1);
        arrayList.add("world");
        assertThat(arrayList.size()).isEqualTo(2);
    }

    @Test
    public void should_return_is_empty_of_array_list() {
        MyArrayList<Integer> arrayList = new MyArrayList<>();
        assertThat(arrayList.isEmpty()).isTrue();
        arrayList.add(1);
        assertThat(arrayList.isEmpty()).isFalse();
    }

    @Test
    public void should_return_is_array_list_contains_element() {
        MyArrayList<Integer> arrayList = new MyArrayList<>();
        assertThat(arrayList.isEmpty()).isTrue();
        arrayList.add(1);
        assertThat(arrayList.isEmpty()).isFalse();
        assertThat(arrayList.contains(1)).isTrue();
        assertThat(arrayList.contains(0)).isFalse();
    }

    @Test
    public void should_print_array_list_use_iterator_for() {
        MyArrayList<Integer> arrayList = new MyArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        int i = 1;
        for (Integer a : arrayList) {
            assertThat(a).isEqualTo(i++);
        }
    }

    @Test
    public void should_clear_array_list() {
        MyArrayList<Integer> arrayList = new MyArrayList<>();
        assertThat(arrayList.isEmpty()).isTrue();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.clear();
        assertThat(arrayList.isEmpty()).isTrue();
    }

    @Test
    public void should_add_element_of_array_list() {
        MyArrayList<String> arrayList = new MyArrayList<>();
        assertThat(arrayList.size()).isEqualTo(0);
        arrayList.add("hello");
        assertThat(arrayList.size()).isEqualTo(1);
        arrayList.add("world");
        assertThat(arrayList.size()).isEqualTo(2);
        assertThat(arrayList.get(1)).isEqualTo("world");
    }

    @Test
    public void should_add_element_of_array_list_given_index() {
        MyArrayList<String> arrayList = new MyArrayList<>();
        assertThat(arrayList.size()).isEqualTo(0);
        arrayList.add("hello");
        assertThat(arrayList.size()).isEqualTo(1);
        arrayList.add("world");
        assertThat(arrayList.size()).isEqualTo(2);
        arrayList.add(0, "!");
        assertThat(arrayList.size()).isEqualTo(3);
        assertThat(arrayList.get(0)).isEqualTo("!");
        assertThat(arrayList.get(1)).isEqualTo("hello");
        assertThat(arrayList.get(2)).isEqualTo("world");
    }

    @Test
    public void should_delete_element_of_array_list_given_index() {
        MyArrayList<String> arrayList = new MyArrayList<>();
        arrayList.add("hello");
        arrayList.add("world");
        assertThat(arrayList.remove(1)).isTrue();
        assertThat(arrayList.size()).isEqualTo(1);
        assertThat(arrayList.remove(0)).isTrue();
        assertThat(arrayList.size()).isEqualTo(0);
    }

    @Test
    public void should_set_element_of_array_list_given_index_and_element() {
        MyArrayList<String> arrayList = new MyArrayList<>();
        arrayList.add("hello");
        arrayList.add("world");
        assertThat(arrayList.set(1, "lisa")).isTrue();
        assertThat(arrayList.size()).isEqualTo(2);
        assertThat(arrayList.get(1)).isEqualTo("lisa");
    }

    @Test
    public void should_get_element_of_array_list_given_index() {
        MyArrayList<String> arrayList = new MyArrayList<>();
        arrayList.add("hello");
        arrayList.add("world");
        assertThat(arrayList.get(0)).isEqualTo("hello");
        assertThat(arrayList.get(1)).isEqualTo("world");
    }
}

二、基于链表实现自定义的List接口

1.链表介绍
image.png
  • 单链表、双链表、循环单链表
  • 物理存储单元上非连续、没有顺序,各个元素的逻辑顺序根据指针链接的次序;
  • 不需要提前知道数据大小,实现内存动态管理;
  • 元素需要时用new分配内存空间,不需要时delete释放已分配的空间,避免内存空间浪费;
2.简单代码实现
/*基于链表实现自定义List接口*/
public class MyLinkedList<E> implements MyList<E> {
    private int size;
    public Node<E> beginNode;
    public Node<E> endNode;


    public MyLinkedList() {
        beginNode = new Node<>(null, null, null);
        endNode = new Node<>(null, beginNode, null);
        beginNode.next = endNode;
        this.size = 0;
    }

    @Override
    public int size() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return this.size == 0;
    }

    @Override
    public boolean contains(E e) {
        if (e == null) {
            return false;
        }
        for (int i = 0; i < size; i++) {
            Node<E> node = getNode(i);
            if (e.equals(node.e)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void clear() {
        beginNode = new Node<>(null, null, null);
        endNode = new Node<>(null, null, null);
        beginNode.next = endNode;
        this.size = 0;
    }

    @Override
    public void add(E e) {
        add(this.size, e);
    }

    @Override
    public void add(int index, E e) {
        addBefore(getNode(index), e);
    }

    @Override
    public boolean remove(int index) {
        if (index < 0 || index >= this.size) {
            throw new IndexOutOfBoundsException();
        } else {
            return remove(getNode(index));
        }
    }

    private boolean remove(Node<E> node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
        this.size--;
        return true;
    }

    @Override
    public boolean set(int index, E e) {
        if (index < 0 || index >= this.size) {
            throw new IndexOutOfBoundsException();
        } else {
            Node<E> node = getNode(index);
            node.e = e;
            return true;
        }
    }

    @Override
    public E get(int index) {
        return getNode(index).e;
    }

    private Node<E> getNode(int index) {
        Node<E> res;
        if (index < 0 || index > this.size) {
            throw new IndexOutOfBoundsException();
        }
        if (index == this.size
        ) {
            return endNode;
        }
        if (index < this.size / 2) {
            res = beginNode.next;
            for (int i = 0; i < index; i++) {
                res = res.next;
            }
        } else {
            res = endNode;
            for (int i = this.size; i > index; i--) {
                res = res.prev;
            }
        }
        return res;
    }

    private void addBefore(Node<E> prev, E e) {
        Node<E> newNode = new Node<>(e, prev.prev, prev);
        newNode.prev.next = newNode;
        prev.prev = newNode;
        this.size++;
    }

    private static class Node<E> {
        public E e;
        public Node<E> prev;
        public Node<E> next;

        public Node(E element) {
            this.e = element;
        }

        public Node(E element, Node<E> prev, Node<E> next) {
            this.e = element;
            this.prev = prev;
            this.next = next;
        }
    }
}
/*自定义LinkedList 测试类*/
public class MyLinkedListTest {
    @Test
    public void should_return_size_of_linked_list() {
        MyLinkedList<String> linkedList = new MyLinkedList<>();
        assertThat(linkedList.size()).isEqualTo(0);
        linkedList.add("hello");
        assertThat(linkedList.size()).isEqualTo(1);
        linkedList.add("world");
        assertThat(linkedList.size()).isEqualTo(2);
    }

    @Test
    public void should_return_is_empty_of_linked_list() {
        MyLinkedList<Integer> linkedList = new MyLinkedList<>();
        assertThat(linkedList.isEmpty()).isTrue();
        linkedList.add(1);
        assertThat(linkedList.isEmpty()).isFalse();
    }

    @Test
    public void should_return_is_linked_list_contains_element() {
        MyLinkedList<Integer> linkedList = new MyLinkedList<>();
        assertThat(linkedList.isEmpty()).isTrue();
        linkedList.add(1);
        assertThat(linkedList.isEmpty()).isFalse();
        assertThat(linkedList.contains(1)).isTrue();
        assertThat(linkedList.contains(0)).isFalse();
    }

    @Test
    public void should_print_linked_list_use_iterator_for() {
    }

    @Test
    public void should_clear_linked_list() {
        MyLinkedList<Integer> linkedList = new MyLinkedList<>();
        assertThat(linkedList.isEmpty()).isTrue();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        linkedList.clear();
        assertThat(linkedList.isEmpty()).isTrue();
    }

    @Test
    public void should_add_element_of_linked_list() {
        MyLinkedList<String> linkedList = new MyLinkedList<>();
        assertThat(linkedList.size()).isEqualTo(0);
        linkedList.add("hello");
        assertThat(linkedList.size()).isEqualTo(1);
        linkedList.add("world");
        assertThat(linkedList.size()).isEqualTo(2);
        assertThat(linkedList.get(1)).isEqualTo("world");
    }

    @Test
    public void should_add_element_of_linked_list_given_index() {
        MyLinkedList<String> linkedList = new MyLinkedList<>();
        assertThat(linkedList.size()).isEqualTo(0);
        linkedList.add("hello");
        assertThat(linkedList.size()).isEqualTo(1);
        linkedList.add("world");
        assertThat(linkedList.size()).isEqualTo(2);
        linkedList.add(0, "!");
        assertThat(linkedList.size()).isEqualTo(3);
        assertThat(linkedList.get(0)).isEqualTo("!");
        assertThat(linkedList.get(1)).isEqualTo("hello");
        assertThat(linkedList.get(2)).isEqualTo("world");
    }

    @Test
    public void should_delete_element_of_linked_list_given_index() {
        MyLinkedList<String> linkedList = new MyLinkedList<>();
        linkedList.add("hello");

        linkedList.add("world");
        assertThat(linkedList.remove(1)).isTrue();
        assertThat(linkedList.size()).isEqualTo(1);
        assertThat(linkedList.remove(0)).isTrue();
        assertThat(linkedList.size()).isEqualTo(0);
    }

    @Test
    public void should_set_element_of_linked_list_given_index_and_element() {
        MyLinkedList<String> linkedList = new MyLinkedList<>();
        linkedList.add("hello");
        linkedList.add("world");
        assertThat(linkedList.set(1, "lisa")).isTrue();
        assertThat(linkedList.size()).isEqualTo(2);
        assertThat(linkedList.get(1)).isEqualTo("lisa");
    }

    @Test
    public void should_get_element_of_linked_list_given_index() {
        MyLinkedList<String> linkedList = new MyLinkedList<>();
        linkedList.add("hello");
        linkedList.add("world");
        assertThat(linkedList.get(0)).isEqualTo("hello");
        assertThat(linkedList.get(1)).isEqualTo("world");
    }
}

三、基于哈希表实现自定义的Map接口

1.哈希表介绍
image.png
  • hash table 根据关键码值key value值之间进行访问;
  • 将关键码值映射到表中的一个位置来访问记录,映射函数为散列函数,存放记录的数组叫散列表;
  • 左边是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。
  • 哈希表运算得非常快,不论哈希表中有多少数据,查找、插入、删除(有时包括删除)只需要接近常量的时间即0(1)的时间级;
  • 基于数组,数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重;
2.简单代码实现
/*自定义Map接口*/
public interface MyMap<K, V> {
    V put(K key, V value);

    V remove(K key);

    V get(K key);

    int size();

    public interface Entry<K, V> {
        K getKey();

        V getValue();
    }
}
/*基于哈希表实现自定义Map接口*/
public class MyHashMap<K, V> implements MyMap<K, V> {
    private static int defaultSize = 10;

    private Entry<K, V>[] table;
    private int size = 0;

    public MyHashMap() {
        this(defaultSize);
    }

    public MyHashMap(int length) {
        defaultSize = length;
        table = new Entry[defaultSize];
    }

    @Override
    public V put(K key, V value) {
        int index = hash(key);
        Entry<K, V> entry = table[index];
        if (entry == null) {
            table[index] = new Entry<>(key, value, null);
        } else {
            while (entry != null) {
                if (entry.getKey() == key) {
                    V oldValue = entry.getValue();
                    entry.v = value;
                    return oldValue;
                }
                entry = entry.next;
            }
            table[index] = new Entry<>(key, value, entry);
        }
        size++;
        return table[index].getValue();
    }

    @Override
    public V remove(K key) {
        int index = hash(key);
        Entry<K, V> entry = table[index];
        if (entry.getKey() == key) {
            table[index] = null;
            size--;
            return entry.getValue();
        }

        while (entry.next != null) {
            if (entry.next.getKey() == key) {
                V oldValue = entry.next.getValue();
                entry.next = entry.next.next;
                size--;
                return oldValue;
            }
            entry = entry.next;
        }
        return null;
    }

    @Override
    public V get(K key) {
        int index = hash(key);
        Entry<K, V> entry = table[index];
        while (entry != null) {
            if (entry.getKey() == key) {
                return entry.getValue();
            }
            entry = entry.next;
        }
        return null;
    }

    @Override
    public int size() {
        return size;
    }

    private int hash(K k) {
        int m = defaultSize;
        int i = k.hashCode() % m;
        return i > 0 ? i : -1;
    }

    class Entry<K, V> implements MyMap.Entry<K, V> {
        K k;
        V v;
        Entry<K, V> next;

        public Entry(K k, V v, Entry<K, V> next) {
            this.k = k;
            this.v = v;
            this.next = next;
        }

        @Override
        public K getKey() {
            return k;
        }

        @Override
        public V getValue() {
            return v;
        }
    }
}


/*自定义HashMap测试类*/
public class MyHashMapTest {
    @Test
    public void should_return_size_of_hash_map() {
        MyHashMap<Integer, String> map = new MyHashMap<>();
        assertThat(map.size()).isEqualTo(0);
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");
        map.put(4, "three");
        map.put(5, "one");
        map.put(6, "two");
        map.put(7, "three");
        map.put(8, "three");
        map.put(9, "one");
        assertThat(map.size()).isEqualTo(9);
    }

    @Test
    public void should_remove_element_of_hash_map() {
        MyHashMap<Integer, String> map = new MyHashMap<>();
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");
        assertThat(map.size()).isEqualTo(3);
        map.remove(1);
        assertThat(map.size()).isEqualTo(2);
        map.remove(3);
        assertThat(map.size()).isEqualTo(1);
    }

    @Test
    public void should_return_element_of_hash_map_given_key() {
        MyHashMap<Integer, String> map = new MyHashMap<>();
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");
        assertThat(map.get(1)).isEqualTo("one");
        assertThat(map.get(2)).isEqualTo("two");
        assertThat(map.get(3)).isEqualTo("three");
    }
}

四、小结

1.数组、链表、哈希表的小结
(1)存储空间上:

链表链表中的元素在内存中不是顺序存储的,而是通过元素中的指针联系到一起,存放的内存空间可以是连续的,也可以是不连续的;数组则是将元素在内存中连续存放。一般情况下存放相同多的数据时,数组占用较小的内存,而链表还需要存放其前驱和后继的空间。

(2)长度的可变性:
数组必须事先定义固定的长度,不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数,会出现溢出现象;当数据减少时,造成内存浪费。链表的长度是按实际需要可以伸缩的,动态地进行存储分配,可以适应数据动态地增减的情况。
(3)查找效率:

按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n); 
按值查找时,若数组无序,数组和链表时间复杂度均为O(n),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);

(4)插入删除时:

数组平均需要移动n/2个元素,而链表只需修改指针即可;

(5)空间分配方面:

(静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小。 链表从堆中分配空间, 自由度大但是申请管理比较麻烦。

(6)哈希表既能够具备数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势:
2.ArrayList、LinkedList、HashMap的小结
  • List的特征是其元素以线性方式存储,集合中可以存放重复对象。
  • List接口主要实现类包括:
  • ArrayList() : 代表长度可以改变得数组。可以对元素进行随机的访问,向ArrayList()中插入与删除元素的速度慢。
  • LinkedList(): 在实现中采用链表数据结构。插入和删除速度快,访问速度慢。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345