ArrayList和LinkedList都是实现了List接口的容器类,用于存储一系列的对象引用。下面我们通过源码(模拟)来分析下它们的底层实现。
首先,需要一个接口List(模拟):
public interface List {
//返回线性表的大小,及数据元素的个数
int size();
//返回线性表中序号为i的数据元素
Object get(int i);
//如果线性表为空返回true,否则返回false
boolean isEmpty();
//判断线性表是否包含元素e
boolean contains();
//返回元素e在线性表中的序号
int indexOf(Object e);
//将元素e插入到线性表中的i位置
void add(int i,Object e);
//将元素e插入到线性表中的末尾
void add(Object e);
//将元素e插入到元素obj之前
boolean addBefore(Object obj,Object e);
//将元素e插入到元素obj之后
boolean addAfter(Object obj,Object e);
//删除线性表中序号为i的元素,并返回值
Object remove(int i);
//删除线性表中第一个与e相同的元素
Object remove(Object e);
//替换线性表中序号为i的数据元素为e,返回元数据元素
Object replace(int i,Object obj);
}
接下来,我们实现顺序表ArrayList(模拟)
import java.util.Arrays;
import com.cshg.exception.MyArrayIndexOutOfBoundsException;
public class ArrayList implements List{
private Object[] elementData; //底层就是一个数组
private int size; //元素个数
public ArrayList() {
//没有指定长度,默认为10
this(10);
}
public ArrayList(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
//集合大小
@Override
public int size() {
return size;
}
//获取下标为i的元素
@Override
public Object get(int i) {
if(i < 0 || i >= size){
return new MyArrayIndexOutOfBoundsException("下标越界异常:"+i);
}
return elementData[i];
}
//是否为空
@Override
public boolean isEmpty() {
return size == 0;
}
//包含
@Override
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
@Override
public int indexOf(Object o) {
if(o == null){
for(int i = 0; i< size; i++)
if(elementData[i] == null)
return i;
}else{
for(int i = 0; i< size; i++)
if(o.equals(elementData[i]))
return i;
}
return -1;
}
public void rangeCheck(int i) throws MyArrayIndexOutOfBoundsException {
if(i < 0 || i >= size){
throw new MyArrayIndexOutOfBoundsException("下标越界异常:"+i);
}
}
public void rangeCheckForAdd(int i) throws MyArrayIndexOutOfBoundsException {
if(i < 0 || i > size){
throw new MyArrayIndexOutOfBoundsException("下标越界异常:"+i);
}
}
@Override
public void add(int i, Object e) throws MyArrayIndexOutOfBoundsException {
rangeCheckForAdd(i);
//元素后移
for (int j = size; j > i; j--) {
elementData[j] = elementData[j-1];
}
elementData[i] = e;
size++;
//扩容
grow(size);
}
//扩容
public void grow(int size){
if(size > elementData.length){
int number = (int)(size + size>>1);
Object[] newArray = new Object[number];
newArray = Arrays.copyOf(elementData,number);
elementData = newArray;
}
}
@Override
public void add(Object o) {
elementData[size] = o;
size++;
//扩容
grow(size);
}
@Override
public boolean addBefore(Object obj, Object e) throws MyArrayIndexOutOfBoundsException {
int i = indexOf(obj);
if(i > -1){
add(i,e);
return true;
}
return false;
}
@Override
public boolean addAfter(Object obj, Object e) throws MyArrayIndexOutOfBoundsException {
int i = indexOf(obj);
if(i > -1){
add(i+1,e);
return true;
}
return false;
}
@Override
public Object remove(int i) throws MyArrayIndexOutOfBoundsException {
rangeCheck(i);
//前移
for (int j = i; j <size-1; j++) {
elementData[j] = elementData[j+1];
}
elementData[size-1] = null;
size--;
return elementData;
}
@Override
public Object remove(Object e) throws MyArrayIndexOutOfBoundsException {
int i = indexOf(e);
if(i > -1){
remove(i);
}
return elementData;
}
@Override
public Object replace(int i, Object obj) throws MyArrayIndexOutOfBoundsException{
rangeCheck(i);
elementData[i] = obj;
return elementData;
}
}
分析:
成员变量
private Object[] elementData; //底层就是一个数组
private int size; //元素个数
size表示elementData数组的实际元素个数,elementData.length表示数组的最大集合容量。
构造函数
public ArrayList() {
//没有指定长度,默认为10
this(10);
}
public ArrayList(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
无参构造函数,调用有参构造函数,并设置默认长度。
操作方法
add操作
public void add(Object o) {
elementData[size] = o;
size++;
//扩容
dilatation(size);
}
该函数插入数据时,会在后面插入,这就保证了数据的有序性。
public void add(int i, Object e) throws MyArrayIndexOutOfBoundsException {
rangeCheckForAdd(i);
//元素后移
for (int j = size; j > i; j--) {
elementData[j] = elementData[j-1];
}
elementData[i] = e;
size++;
//扩容
dilatation(size);
}
该函数用于中间插入数据,但是插入该数据之前,还要做很多次的元素后移操作,即自i至最后的每个元素都必须向后挪一个位置,因此中间进行数据插入的性能很低。
扩容
//扩容
public void grow(int size){
if(size > elementData.length){//如果实际元素个数大于数组的最大容量时,则进行扩容
int number = (int)(size + size>>1);
Object[] newArray = new Object[number];
newArray = Arrays.copyOf(elementData,number);
elementData = newArray;
}
}
进行add操作的时候,会调用扩容的方法,当size>elementData.length时,则进行扩容, 扩容后大小为原始容量的 1.5 倍。且看实现:新建一个长度为原来1.5倍的数组,将之前的数组的元素拷贝给新数组,最后将新数组赋值给elementData,即elementData的指针指向新数组。
remove操作
@Override
public Object remove(int i) throws MyArrayIndexOutOfBoundsException {
rangeCheck(i);
//前移
for (int j = i; j <size-1; j++) {
elementData[j] = elementData[j+1];
}
elementData[size-1] = null;
size--;
return elementData;
}
该函数会将第i个元素进行删除,在删除之前还要做很多次的元素前移操作,即自i至最后的每个元素都必须向前挪一个位置,因此中间进行数据删除的性能很低。
get操作
//获取下标为i的元素
@Override
public Object get(int i) {
if(i < 0 || i >= size){
return new MyArrayIndexOutOfBoundsException("下标越界异常:"+i);
}
return elementData[i];
}
直接调用数组随机访问,性能很好。
单链表
单链表代码模拟实现
public class MyLinkedList {
private MyNode head;
private int size;
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
public void addHead(Object data){
MyNode newNode = new MyNode(data);
newNode.next = head;
head = newNode;
size++;
}
public void add(Object data,int position) throws MyArrayIndexOutOfBoundsException{
checkPosition(position);
if(position == 0){
addHead(data);
}else{
MyNode cur = getCur(position);
MyNode newNode = new MyNode(data);
newNode.next = cur.next;//当前节点的next赋给新节点的next
cur.next = newNode;//当前节点next指向新节点
size++;
}
}
public void delHead(){
head = head.next;
}
public void delete(int position) throws MyArrayIndexOutOfBoundsException{
checkPosition(position);
if(position == 0){
delHead();
}else{
MyNode cur = getCur(position);
cur.next = cur.next.next;
size--;
}
}
public void checkPosition(int position) throws MyArrayIndexOutOfBoundsException{
if(position<0 || position > size){
throw new MyArrayIndexOutOfBoundsException("-1", "指针越界");
}
}
public MyNode getCur(int position) throws MyArrayIndexOutOfBoundsException{
checkPosition(position);
MyNode cur = head;
for (int i = 1; i < position; i++) {
cur = cur.next; //一直往后遍历,直到找到插入的位置的上个节点(在此称为当前节点)
}
return cur;
}
public Object get(int position) throws MyArrayIndexOutOfBoundsException{
return getCur(position+1).getValue();
}
class MyNode{
Object value;
MyNode next; //下一个指针
public MyNode(Object value){
this.value = value;
}
public MyNode(Object value,MyNode next){
this.value = value;
this.next = next;
}
public Object getValue(){
return value;
}
}
}
add操作
1.addHead
public void addHead(Object data){
MyNode newNode = new MyNode(data);
newNode.next = head;
head = newNode;
size++;
}
添加头节点:创建一个新节点,新节点的后继指向原先的头节点,引用变量head指向新节点。请看图解:
2.add
public void add(Object data,int position) throws MyArrayIndexOutOfBoundsException{
checkPosition(position);
if(position == 0){
addHead(data);
}else{
MyNode cur = getCur(position);
MyNode newNode = new MyNode(data);
newNode.next = cur.next;//当前节点的next赋给新节点的next
cur.next = newNode;//当前节点next指向新节点
size++;
}
}
public MyNode getCur(int position) throws MyArrayIndexOutOfBoundsException{
checkPosition(position);
MyNode cur = head;
for (int i = 1; i < position; i++) {
cur = cur.next; //一直往后遍历,直到找到插入的位置的上个节点(在此称为当前节点)
}
return cur;
}
插入数据:找到插入的位置的上一个节点(我们就叫它为当前节点吧),将当前节点的后继赋给新节点的后继,当前节点的后继指向新节点。图解:
remove操作
deleteHead
public void delHead(){
head = head.next;
}
删除头结点:head = head.next,结合下图理解。
如上图所示,dataA所在的节点为头节点,删除头节点之后,那么头节点就变成了dataB所在的节点了,写成代码不就是head = head.next;