源码分析系列教程(09) - 手写List框架

简介: 源码分析系列教程(09) - 手写List框架

代码已上传到GitHub,有兴趣的同学可以下载来看看:https://github.com/ylw-github/Java-CodeAnalysis-Demo

1.集合框架的介绍

下面先来看下两张图:

从上面的框架图可以看出:

  1. 所有集合类都位于java.util包下:Java的集合类主要由两个接口派生而出:CollectionMap,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
  2. 集合接口 :6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。
  3. 抽象类 :5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。
  4. 实现类 :8个实现类(实线表示),对接口的具体实现。
  5. Collection接口 是一组允许重复的对象。
  6. Set接口 继承 Collection,集合元素不重复。
  7. List接口 继承 Collection,允许重复,维护元素插入顺序。
  8. Map接口 是键-值对象,与Collection接口没有什么关系。

Set、List和Map可以看做集合的三大类:

  • List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
  • Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。
  • Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。

2. 手写List框架

List集合代表一个有序集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。

List接口继承于Collection接口,它可以定义一个允许重复的有序集合。因为List中的元素是有序的,所以我们可以通过使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。

List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack

2.1 底层实现原理

2.1.2 ArrayList底层实现原理

1. Arraylist底层基于数组实现:

private Object[] elementData;

2. Arraylist底层默认数组初始化大小为10个object数组:

public ExtArraylist() throws Exception {
  this(10);
}
public ExtArraylist(int initialCapacity) throws Exception {
  if (initialCapacity < 0) {
    throw new IllegalArgumentException("初始容量不能小于0 " + initialCapacity);
  }
  elementData = new Object[initialCapacity];
}

3. 添加元素后大于当前数组的长度,则进行扩容,将数组的长度增加原来数组的一半:

// 增大数组空间
private void grow(int minCapacity) {
  // overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 在原来容量的基础上加上
                          // oldCapacity/2
if (newCapacity - minCapacity < 0)
  newCapacity = minCapacity; // 最少保证容量和minCapacity一样
if (newCapacity - MAX_ARRAY_SIZE > 0)
  newCapacity = hugeCapacity(minCapacity); // 最多不能超过最大容量
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.1.3 Vector底层实现原理

Vector是线程安全的,但是性能比ArrayList要低。

ArrayList,Vector主要区别为以下几点:

1. Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比;


2. ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍;


3. Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。

2.1.4 实现

自定义List接口:

package com.ylw.jdk.list;
public interface ExtList<E> {
  public void add(E object);
  public void add(int index, E object);
  public Object remove(int index);
  public boolean remove(E object);
  public int getSize();
  public Object get(int index);
}

手写ArrayList:

package com.ylw.jdk.list;
import java.util.Arrays;
public class ExtArrayList<E> implements ExtList<E> {
  // 保存ArrayList中数据的数组
  private transient Object[] elementData;
  // ArrayList实际数量
  private int size;
  public ExtArrayList() {
    // 默认初始容量为10
    this(10);
  }
  public ExtArrayList(int initialCapacity) {
    if (initialCapacity < 0) {
      throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
    // 初始化数组容量
    elementData = new Object[initialCapacity];
  }
  // 添加方法实现
  public void add(Object object) {
    ensureExplicitCapacity(size + 1);
    elementData[size++] = object;
  }
  public void add(int index, Object object) {
    rangeCheck(index);
    ensureExplicitCapacity(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = object;
    size++;
  }
  public void ensureExplicitCapacity(int minCapacity) {
    // 如果存入的数据,超出了默认数组初始容量 就开始实现扩容
    if (size == elementData.length) {
      // 获取原来数组的长度 2
      int oldCapacity = elementData.length;
      // oldCapacity >> 1 理解成 oldCapacity/2 新数组的长度是原来长度1.5倍
      int newCapacity = oldCapacity + (oldCapacity >> 1); // 3
      if (newCapacity < minCapacity) {
        // 最小容量比新容量要小的,则采用初始容量minCapacity
        newCapacity = minCapacity;
      }
      // System.out.println("oldCapacity:" + oldCapacity + ",newCapacity:"
      // + newCapacity);
      elementData = Arrays.copyOf(elementData, newCapacity);
    }
  }
  // public void add(Object object) {
  // elementData[size++] = object;
  // // 如果存入的数据,超出了默认数组初始容量 就开始实现扩容
  // if (size == elementData.length) {
  // // 新的数组容量
  // int newCapacity = 2 * size;
  // // 实现数组扩容
  // Object[] newObjct = new Object[newCapacity];
  // for (int i = 0; i < elementData.length; i++) {
  // newObjct[i] = elementData[i];
  // }
  // elementData = newObjct;
  // }
  // }
  // 获取数据
  public Object get(int index) {
    rangeCheck(index);
    return elementData[index];
  }
  public Object remove(int index) {
    Object object = get(index);
    int numMoved = elementData.length - index - 1;
    if (numMoved > 0)
      System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    elementData[--size] = null;
    return object;
  }
  public boolean remove(E object) {
    for (int i = 0; i < elementData.length; i++) {
      Object element = elementData[i];
      if (element.equals(object)) {
        remove(i);
        return true;
      }
    }
    return false;
  }
  private void rangeCheck(int index) {
    if (index >= size) {
      throw new IndexOutOfBoundsException("数组越界啦!");
    }
  }
  public int getSize() {
    return size;
  }
}

3.知识补充

Java运算符:

<<      :     左移运算符,num << 1,相当于num乘以2
>>      :     右移运算符,num >> 1,相当于num除以2
>>>     :     无符号右移,忽略符号位,空位都以0补齐

数组拷贝:

  1. Arrays.copyOf 功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度。
  2. System.arraycopy 方法:如果是数组比较大,那么使用System.arraycopy会比较有优势,因为其使用的是内存复制,省去了大量的数组寻址访问等时间复制指定源数组src到目标数组dest。复制从src的srcPos索引开始,复制的个数是length,复制到dest的索引从destPos开始。

src(源数组) : srcPos源数组要复制的起始位置;

dest(目的数组) : destPos目的数组放置的起始位置; length:复制的长度。

src and dest都必须是同类型或者可以进行转换类型的数组,有趣的是这个函数可以实现自己到自己复制,比如:

int[] fun ={0,1,2,3,4,5,6}; 
System.arraycopy(fun,0,fun,3,3);

结果为:{0,1,2,0,1,2,6};

实现过程:先生成一个长度为length的临时数组,将fun数组中srcPos 到srcPos+length-1之间的数据拷贝到临时数组中,再执行System.arraycopy(临时数组,0,fun,3,3).

总结

目录
相关文章
|
2月前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
64 5
|
4月前
|
存储 安全 Java
java集合框架复习----(2)List
这篇文章是关于Java集合框架中List集合的详细复习,包括List的特点、常用方法、迭代器的使用,以及ArrayList、Vector和LinkedList三种实现类的比较和泛型在Java中的使用示例。
java集合框架复习----(2)List
|
4月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set
|
4月前
|
测试技术 索引 Python
Python接口自动化测试框架(基础篇)-- 常用数据类型list&set()
本文介绍了Python中list和set两种数据类型的使用,包括它们的创建、取值、增删改查操作、排序以及内置函数的使用,还探讨了list的比较函数和set的快速去重功能。
37 0
|
6月前
|
存储 索引 Python
Python教程:深入了解 Python 中 Dict、List、Tuple、Set 的高级用法
Python 中的 Dict(字典)、List(列表)、Tuple(元组)和 Set(集合)是常用的数据结构,它们各自有着不同的特性和用途。在本文中,我们将深入了解这些数据结构的高级用法,并提供详细的说明和代码示例。
267 2
|
6月前
|
Java 索引
JavaSE——集合框架一(3/7)-List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理
JavaSE——集合框架一(3/7)-List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理
45 2
|
6月前
|
Java
JavaSE——集合框架一(4/7)-List系列集合:LinkedList集合的底层原理、特有方法、队列、栈
JavaSE——集合框架一(4/7)-List系列集合:LinkedList集合的底层原理、特有方法、队列、栈
48 0
|
7月前
|
前端开发
css教程-li的list-style-type属性
通过设置 `list-style-type`属性,你可以根据需求为列表项设置不同的标志样式,从而改变列表的外观。 买CN2云服务器,免备案服务器,高防服务器,就选蓝易云。百度搜索:蓝易云
75 4
|
7月前
|
存储 数据处理 索引
Python基础教程——列表(List)
Python基础教程——列表(List)
|
7月前
|
Python
两个list集合合并成一个python教程 - 蓝易云
在这两种方法中,加号会创建一个新的列表,而extend方法则会在原地修改列表。
45 0