List

描述

线性表抽象接口,所有线性表应该在实现这个接口的基础上进行操作。

接口

package list;/*** Description: 线性表的接口,使用泛型保证类型** @ClassName: List* @author 过道* @date 2018年8月13日 上午10:45:13*/
public interface ListInterface<T> {public void add(T newEntry);public void add(Integer newPosition, T newEntry);public T remove(int givePosition);public void clear();public T replace(int givenPosition, T newEntry);public T getEntry(int givenPosition);public T[] toArray();public boolean contains(T anEntry);public int getLength();public boolean isEmpty();
}

ArrayList

1 接口

/*** 为了区分java默认的 ArrayList和我的list,所以命名为AList* 因为内部没有实现,所以很简单,就没有写注释*/
public class AList<T> implements ListInterface<T> {@Overridepublic void add(T newEntry) {}@Overridepublic void add(Integer newPosition, T newEntry) {}@Overridepublic T remove(int givePosition) {return null;}@Overridepublic void clear() {}@Overridepublic T replace(int givenPosition, T newEntry) {return null;}@Overridepublic T getEntry(int givenPosition) {return null;}@Overridepublic T[] toArray() {return null;}@Overridepublic boolean contains(T anEntry) {return false;}@Overridepublic int getLength() {return 0;}@Overridepublic boolean isEmpty() {return false;}}

 

2.实现途径

Array的意思就是数组,所以很简单可以猜测到这是使用数组实现List,为了知道数组中多少个有效位,使用一个int值保存。初始化时,要么用户给定数组容量,要么使用默认容量

注:强烈建议在使用ArrayList或其他双倍扩容的容器时给定容量,因为这将大大节省扩容的时间。

public class AList<T> implements ListInterface<T> {//空数组,用来存放数据T[] list = null;// 当前有效的数字个数int numberOfEntries = 0;//如果用户不给定容量(强烈建议初始化时给定容量), 默认的数组容量static final int DEFAULT_CAPACITY = 27;@SuppressWarnings("unchecked")public AList(int initialCapacity) {T[] tempList = (T[]) new Object[initialCapacity];list = tempList;numberOfEntries = 0;}public AList() {this(DEFAULT_CAPACITY);}// 其余覆盖的方法
}

 

实现add方法

注意一下,我们使用下标从1开始,所以不要与ArrayList混淆(从0开始)。

注:ArrayList数组使用的是 1.5 倍扩容,即如果当前数组空间已满,我们申请双倍容量的数组,然后将原数组copy到新数组中,所以扩容是比较慢的。

Vector使用的是2倍扩容,不过我这里为了省事,使用了2倍扩容。

    @Overridepublic void add(T newEntry) {// 与ArrayList 不同,我们选择下标从 1 开始。list[numberOfEntries + 1] = newEntry;numberOfEntries++;ensureCapacity();//也可以选择重用其他方法
//        add(numberOfEntries+1,newEntry);
// 这样重用,能有效减少代码,但是理解上稍微有些困难。并且增加了许多无谓的判断,降低了效率(当然这无所谓)
    }/*** 指定位置进行添加某一个数字** @param newPosition* @param newEntry*/@Overridepublic void add(Integer newPosition, T newEntry) {if ((newPosition >= 1) && (newPosition <= numberOfEntries + 1)) {if (newPosition <= numberOfEntries) {makeRoom(newPosition);  // 将给定位置的数字及后续数字全部后移。
            }list[newPosition] = newEntry;numberOfEntries++;ensureCapacity();   // 为下次添加获取足够的空间} else {throw new IndexOutOfBoundsException("给定位置不合法,下标越界");}}/*** @param newPosition*/private void makeRoom(Integer newPosition) {assert (newPosition >= 1) && (newPosition <= newPosition + 1);  //断言,如果不满足断言,则报错,需要手动开启assertint newIndex = newPosition;int lastIndex = numberOfEntries;for (int index = 0; index < newIndex; index++) {list[index + 1] = list[index];  //给定位置之后的数字全部后移一位,将给定位置‘空’下来
        }}/*** 倍扩容量*/private void ensureCapacity() {int capacity = list.length - 1;if (numberOfEntries >= capacity) {int newCapacity = 2 * capacity;list = Arrays.copyOf(list, newCapacity + 1);}}

 

实现简单方法

只要看懂了前面的底层实现,那么对于下面三个方法的实现应该不会有问题。

    @Overridepublic int getLength() {return numberOfEntries;}@Overridepublic boolean isEmpty() {return numberOfEntries == 0;}@Overridepublic void clear() {list = null;    //取消引用,等待GC回收numberOfEntries = 0;}

remove方法

数组移除一个元素后,那么后续元素立刻跟进,弥补空隙。

注意:已经扩容后的容量不会被返还,也没有必要返还。

如果真想归还的话,:源码中提供了“trimToSize()”,当然具体应用因人而异。

    @Overridepublic T remove(int givePosition) {if ((givePosition >= 1) && (givePosition <= numberOfEntries)) {assert !isEmpty();T result = list[givePosition];if (givePosition < numberOfEntries) {removeGap(givePosition);    // 移除后出现空隙,我们让之后的元素前移一位,弥补空隙
            }return result;}return null;}/*** 给定位置为空隙,后续元素前进一位,补上空隙*/private void removeGap(int givePosition) {assert (givePosition >= 1) && (givePosition < numberOfEntries);int removedIndex = givePosition;int lastIndex = numberOfEntries;for (int index = givePosition; index < lastIndex; index++) {list[index] = list[index + 1];}}

tip: trimToSize的源码

/*** Trims the capacity of this <tt>ArrayList</tt> instance to be the* list's current size.  An application can use this operation to minimize* the storage of an <tt>ArrayList</tt> instance.*/public void trimToSize() {modCount++;if (size < elementData.length) {elementData = (size == 0)? EMPTY_ELEMENTDATA    // 这是个空数组 ---> {}: Arrays.copyOf(elementData, size);  //吧数字copy进去,给一个size容量}}

 

toArray方法的实现

注意:千万不要直接返回ArrayList底层的数组,我们应该新申请一个空间,并将元素逐个放入其中。如果返回list,将成为一个非常恐怖的事情。

封装性:其他类只能通过我们定义好的方法去访问或改变我们对象中的域,不然就是封装遭到了破坏.

@Overridepublic T[] toArray() {/* 这里千万不能返回我们使用的T[] list* 因为用户可能会对返回数组进行修改,* 如果返回list,那么封装失败。* 也就是说用户可以不使用我们提供的方法进行操作List,这是很恐怖的事情。*/T[] result = (T[]) new Object[numberOfEntries];for (int index = 0; index < numberOfEntries; index++) {result[index] = list[index + 1];}return result;}

其余方法

    @Overridepublic T replace(int givenPosition, T newEntry) {if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) {assert !isEmpty();T oldEntry = list[givenPosition];list[givenPosition] = newEntry;return oldEntry;}//省去报错的部分return null;}@Overridepublic T getEntry(int givenPosition) {// 判断给定位置是否合法,不合法就报错。if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) {assert !isEmpty();return list[givenPosition];}//else就报错,我省去此步return null;}@Overridepublic boolean contains(T anEntry) {boolean found = false;int index = 1;// 遍历寻找,找到一个就结束循环。while (!found && (index <= numberOfEntries)) {if (anEntry.equals(list[index])) {found = true;}index++;}return found;}

全部方法实现源码

package list;import java.util.Arrays;/*** 为了区分java默认的 ArrayList和我的list,所以命名为ArrayList*/
public class AList<T> implements ListInterface<T> {T[] list = null;int numberOfEntries = 0;static final int DEFAULT_CAPACITY = 27;@SuppressWarnings("unchecked")public AList(int initialCapacity) {T[] tempList = (T[]) new Object[initialCapacity];list = tempList;numberOfEntries = 0;}public AList() {this(DEFAULT_CAPACITY);}@Overridepublic void add(T newEntry) {// 与ArrayList 不同,我们选择下标从 1 开始。list[numberOfEntries + 1] = newEntry;numberOfEntries++;ensureCapacity();//也可以选择重用其他方法
//        add(numberOfEntries+1,newEntry);
// 这样重用,能有效减少代码,但是理解上稍微有些困难。并且增加了许多无谓的判断,降低了效率(当然这无所谓)
    }/*** 指定位置进行添加某一个数字** @param newPosition* @param newEntry*/@Overridepublic void add(Integer newPosition, T newEntry) {if ((newPosition >= 1) && (newPosition <= numberOfEntries + 1)) {if (newPosition <= numberOfEntries) {makeRoom(newPosition);  // 将给定位置的数字及后续数字全部后移。
            }list[newPosition] = newEntry;numberOfEntries++;ensureCapacity();   // 为下次添加获取足够的空间} else {throw new IndexOutOfBoundsException("给定位置不合法,下标越界");}}/*** @param newPosition*/private void makeRoom(Integer newPosition) {assert (newPosition >= 1) && (newPosition <= newPosition + 1);  //断言,如果不满足断言,则报错,需要手动开启assertint newIndex = newPosition;int lastIndex = numberOfEntries;for (int index = 0; index < newIndex; index++) {list[index + 1] = list[index];  //给定位置之后的数字全部后移一位,将给定位置‘空’下来
        }}@Overridepublic T remove(int givePosition) {if ((givePosition >= 1) && (givePosition <= numberOfEntries)) {assert !isEmpty();T result = list[givePosition];if (givePosition < numberOfEntries) {removeGap(givePosition);    // 移除后出现空隙,我们让之后的元素前移一位,弥补空隙
            }return result;}return null;}/*** 给定位置为空隙,进行弥补空隙,** @param givePosition*/private void removeGap(int givePosition) {assert (givePosition >= 1) && (givePosition < numberOfEntries);int removedIndex = givePosition;int lastIndex = numberOfEntries;for (int index = givePosition; index < lastIndex; index++) {list[index] = list[index + 1];}}@Overridepublic void clear() {list = null;    //取消引用,等待GC回收numberOfEntries = 0;}@Overridepublic T replace(int givenPosition, T newEntry) {if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) {assert !isEmpty();T oldEntry = list[givenPosition];list[givenPosition] = newEntry;return oldEntry;}return null;}@Overridepublic T getEntry(int givenPosition) {if ((givenPosition >= 1) && (givenPosition <= numberOfEntries)) {assert !isEmpty();return list[givenPosition];}//else就报错,我省去此步return null;}@Overridepublic T[] toArray() {T[] result = (T[]) new Object[numberOfEntries];for (int index = 0; index < numberOfEntries; index++) {result[index] = list[index + 1];}return result;}@Overridepublic boolean contains(T anEntry) {boolean found = false;int index = 1;while (!found && (index <= numberOfEntries)) {if (anEntry.equals(list[index])) {found = true;}index++;}return found;}@Overridepublic int getLength() {return numberOfEntries;}@Overridepublic boolean isEmpty() {return numberOfEntries == 0;}/*** 倍扩容量*/private void ensureCapacity() {int capacity = list.length - 1;if (numberOfEntries >= capacity) {int newCapacity = 2 * capacity;list = Arrays.copyOf(list, newCapacity + 1);}}
}

 

总结

如果说ArrayList 需要注意的地方

  1. 1.5倍扩容,思想很棒,但是比较浪费时间,所以程序员尽可能估算要使用的空间的大概上界,然后初始化时给定容量。
  2. toArray时,尽管底层有数组,但是千万不能返回底层的数组。
  3. clear时,直接将数组引用置为null,GC就可以回收到这块内存了。

1.5倍扩容:源代码:

int newCapacity = oldCapacity + (oldCapacity >> 1);//old + (old容量/ 2)

使用位移,更快。

LinkedList

实现接口

package list;/*** 为了区分LinkedList,所以命名为LList,但本质都是双向链表* 下标从 1 开始,与java源码不同(以0开始)*/
public class LList<T> implements ListInterface<T> {@Overridepublic void add(T newEntry) {}@Overridepublic void add(Integer newPosition, T newEntry) {}@Overridepublic T remove(int givePosition) {return null;}@Overridepublic void clear() {size = 0;first = null;}@Overridepublic T replace(int givenPosition, T newEntry) {return null;}@Overridepublic T getEntry(int givenPosition) {return null;}@Overridepublic T[] toArray() {return null;}@Overridepublic boolean contains(T anEntry) {return false;}@Overridepublic int getLength() {return 0;}@Overridepublic boolean isEmpty() {return false;}}

 

底层实现

明显使用链式,所以我们需要定义结点类Node。

其次,我们刚开始使用单向链表,利于实现与理解,之后会修改部分代码使其成了双向链表。并提供一些双向链表特有的操作。

public class LList<T> implements ListInterface<T> {int size;Node firstNode;// 链式实现无法给定容量,所以一个无参构造即可。public LList() {size = 0;firstNode = null;}//省去覆盖方法// 单向结点,只需要next和data就足够了class Node {T data;Node next;public Node() {}public Node(T data) {this(data, null);}public Node(T data, Node next) {this.data = data;this.next = next;}}
}

 

核心方法:add的实现

    @Overridepublic void add(T newEntry) {// 尾部位置进行添加
        add(size, newEntry);}@Overridepublic void add(Integer newPosition, T newEntry) {Node newNode = new Node(newEntry);if (newPosition == 1) {//头结点插入newNode.next = firstNode;firstNode = newNode;} else if (newPosition > 1 && newPosition <= size) {Node beforeNode = firstNode;int count = 1;// 找到链表对应位置的前一位,进行插入操作while (count != newPosition - 1) {beforeNode = beforeNode.next;count++;}// 插入元素newNode.next = beforeNode.next;    // 链接起来了beforeNode.next = newNode;} else {throw new IndexOutOfBoundsException("给定位置越界");}}

LinkedList全部源码

package list;/*** 为了区分LinkedList,所以命名为LList,但本质都是双向链表* 下标从 1 开始,与java源码不同(以0开始)*/
public class LList<T> implements ListInterface<T> {private int size;private Node firstNode;public LList() {size = 0;firstNode = null;}@Overridepublic void add(T newEntry) {// 尾部位置进行添加
        add(size, newEntry);}@Overridepublic void add(Integer newPosition, T newEntry) {Node newNode = new Node(newEntry);if (newPosition == 1) {//头结点插入newNode.next = firstNode;firstNode = newNode;} else if (newPosition > 1 && newPosition <= size) {Node beforeNode = firstNode;int count = 1;// 找到链表对应位置的前一位,进行插入操作while (count != newPosition - 1) {beforeNode = beforeNode.next;count++;}// 插入元素newNode.next = beforeNode.next;    // 链接起来了beforeNode.next = newNode;} else {throw new IndexOutOfBoundsException("给定位置越界");}}@Overridepublic T remove(int givePosition) {T result;if ((givePosition >= 1) && (givePosition <= size)) {assert !isEmpty();if (givePosition == 1) {// 头结点的移除result = firstNode.data;firstNode = firstNode.next;} else {Node nodeBefore = getNodeAt(givePosition - 1);Node nodeToRemove = nodeBefore.next;result = nodeBefore.data;Node nodeAfter = nodeToRemove.next;nodeBefore.next = nodeAfter;    // 断开移除结点的两侧
            }size--;return result;} elsethrow new IndexOutOfBoundsException("下标越界");}private Node getNodeAt(int i) {if (i > 0 && i <= size) {Node curNode = firstNode;for (int j = 0; j < i; j++)curNode = curNode.next;return curNode;} elsethrow new IndexOutOfBoundsException("下标越界");}@Overridepublic void clear() {size = 0;firstNode = null;}@Overridepublic T replace(int givenPosition, T newEntry) {// 判断和报错都交给 getNodeAt 方法去做Node nodeToReplace = getNodeAt(givenPosition);T result = nodeToReplace.data;nodeToReplace.data = newEntry;return result;}@Overridepublic T getEntry(int givenPosition) {return getNodeAt(givenPosition).data;}@Overridepublic T[] toArray() {T[] result = (T[]) new Object[size];Node curNode = firstNode;for (int i = 0; i < size; i++) {result[i] = curNode.data;}return null;}@Overridepublic boolean contains(T anEntry) {Node curNode = firstNode;boolean found = false;while (!found && curNode != null) {if (curNode.data.equals(anEntry)) {found = true;}curNode = curNode.next;}return found;}@Overridepublic int getLength() {return size;}@Overridepublic boolean isEmpty() {boolean result = false;// 为了提供更多的报错信息,在这里使用断言,需要手动开启断言功能。且程序开发中不建议使用。if (size == 0) {assert firstNode == null;result = true;} else {assert firstNode != null;result = false;}return result;}class Node {T data;Node next;public Node() {}public Node(T data) {this(data, null);}public Node(T data, Node next) {this.data = data;this.next = next;}}
}

接下来只需要把单向链表改成双向链表即可,增加几个“getPrevNode(),addFirstNode()”之类的方法,修改add(),即可。

转载于:https://www.cnblogs.com/guodao/p/9702388.html

线性表ArrayList和LinkedList源码详解。相关推荐

  1. LinkedList源码详解

    public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deq ...

  2. 【java】LinkedList1.8源码详解

    目录 前言 概要 属性 构造方法 核心方法 get(int index) set(int index, E element) add(int index, E element) addAll(Coll ...

  3. java源码详解——String类

    java源码详解--String类目录: Java String 类 下面开始介绍主要方法: Java charAt() 方法 Java compareTo() 方法 int compareTo(St ...

  4. Java集合框架源码详解系列(一)

     写在前面:大家好!我是晴空๓.如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教.我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/.非常 ...

  5. 源码详解Android 9.0(P) 系统启动流程之SystemServer

    源码详解Android 9.0(P) 系统启动流程目录: 源码详解Android 9.0(P)系统启动流程之init进程(第一阶段) 源码详解Android 9.0(P)系统启动流程之init进程(第 ...

  6. 【Live555】live555源码详解(二):BasicHashTable、DelayQueue、HandlerSet

    [Live555]live555源码详解系列笔记 3.BasicHashTable 哈希表 协作图: 3.1 BasicHashTable BasicHashTable 继承自 HashTable 重 ...

  7. cocos android-1,Cocos2D-Android-1之源码详解:5.Box2dTest

    Cocos2D-Android-1之源码详解:5.Box2dTest 发布时间:2020-08-06 06:19:28 来源:51CTO 阅读:398 作者:abab99 package org.co ...

  8. Redis从精通到入门——数据类型Zset实现源码详解

    Redis数据类型之Zset详解 Zset简介 Zset常用操作 应用场景 Zset实现 源码阅读 Zset-ziplist实现 图解Zset-ziplist Zset-字典(dict) + 跳表(z ...

  9. java的数组与Arrays类源码详解

    java的数组与Arrays类源码详解 java.util.Arrays 类是 JDK 提供的一个工具类,用来处理数组的各种方法,而且每个方法基本上都是静态方法,能直接通过类名Arrays调用. 类的 ...

最新文章

  1. 导师:我不会拖延研究生正常毕业
  2. JavaScript 设计模式基础(二)
  3. C++系列总结——构造与析构
  4. 用Quartus II Timequest Timing Analyzer进行时序分析 :实例讲解
  5. Java 结构体之 JavaStruct 使用教程一 初识 JavaStruct
  6. deepin安装windows虚拟机_Deepin Linux V20系统通过安装wine实现运行windows程序
  7. 如何将网站升级为HTTPS协议(整理)
  8. leetcode955. Delete Columns to Make Sorted II
  9. Python 双y轴绘制
  10. Python练习-一辆购物车的寂寞都是Alex的错
  11. LuatOS之LVGL字体篇
  12. 失败的教训,总结下三个多月的考研历程(最终发现调剂非全复试成功了)
  13. Unity3D《打地鼠》学习笔记及心得
  14. 怎么往日历里面加时钟java,怎样在博客里添加钟表和日历
  15. 经典趣味数学问题之过河问题
  16. 李阳英语228句口语要素 +校园英语迷你惯用语 +1000句最常用英语口语
  17. HDOJ 2021-2030
  18. GYM 101128 H.Sheldon Numbers(枚举)
  19. Anaconda超详细安装教程(Windows环境下)
  20. Armadillo使用介绍(一):Armadillo介绍

热门文章

  1. [Delphi] Webbroker ISAPI 示例说明
  2. FineUI利用JS取控件的值
  3. 从指定文件夹里COPY指定的一批文件列表(TXT文件)
  4. Linq to XML 基本类
  5. android Adapter使用详解
  6. detime php_php试题及答案
  7. 在计算机技术中描述信息最小单位是,计算机二级考试单选题
  8. java float内存结构_Java后端开发岗必备技能:Java并发中的内存模型
  9. mysql约束条件整型_MySQL 数据类型(整型,浮点型,字符类型,日期类型,枚举和集合) 约束条件 自增...
  10. 中专是计算机专业毕业论文,中专计算机专业毕业论文内容