ArrayList与顺序表

ArrayList简介

在集合框架中,ArrayList是一个普通的类,实现了List接口。

【说明】

  1. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
  2. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
  3. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
  4. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
  5. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表

ArrayList使用

ArrayList的构造

方法 解释
ArrayList() 无参构造
ArrayList(Collection c) 利用其他 Collection 构建 ArrayList
ArrayList(int initialCapacity) 指定顺序表初始容量
public static void main(String[] args) {
    // ArrayList创建,推荐写法
    // 构造一个空的列表
    List<Integer> list1 = new ArrayList<>();
    
    // 构造一个具有10个容量的列表
    List<Integer> list2 = new ArrayList<>(10);
    list2.add(1);
    list2.add(2);
    list2.add(3);
    // list2.add("hello"); // 编译失败,List已经限定了,list2中只能存储整形元素
    
    // list3构造好之后,与list中的元素一致
    ArrayList<Integer> list3 = new ArrayList<>(list2);
    
    // 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
    List list4 = new ArrayList();
    list4.add("111");
    list4.add(100);
}

ArrayList常见操作

ArrayList虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,同学们自行查看ArrayList的帮助文档

方法 解释
boolean add(E e) 尾插 e
void add(int index, E element) 将 e 插入到 index 位置
boolean addAll(Collection c) 尾插 c 中的元素
E remove(int index) 删除 index 位置元素
boolean remove(Object o) 删除遇到的第一个 o
E get(int index) 获取下标 index 位置元素
E set(int index, E element) 将下标 index 位置元素设置为 element
void clear() 清空
boolean contains(Object o) 判断 o 是否在线性表中
int indexOf(Object o) 返回第一个 o 所在下标
int lastIndexOf(Object o) 返回最后一个 o 的下标
List subList(int fromIndex, int toIndex) 截取部分 list
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("JavaSE");
    list.add("JavaWeb");
    list.add("JavaEE");
    list.add("JVM");
    list.add("测试课程");
    System.out.println(list);
    
    // 获取list中有效元素个数
    System.out.println(list.size());
    
    // 获取和设置index位置上的元素,注意index必须介于[0, size)间
    System.out.println(list.get(1));
    list.set(1, "JavaWEB");
    System.out.println(list.get(1));
    
    // 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
    list.add(1, "Java数据结构");
    System.out.println(list);
    
    // 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
    list.remove("JVM");
    System.out.println(list);
    
    // 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
    list.remove(list.size()-1);
    System.out.println(list);
    
    // 检测list中是否包含指定元素,包含返回true,否则返回false
    if(list.contains("测试课程")){
    	list.add("测试课程");
    } 
    // 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
    list.add("JavaSE");
    System.out.println(list.indexOf("JavaSE"));
    System.out.println(list.lastIndexOf("JavaSE"));
    
    // 使用list中[0, 4)之间的元素构成一个新的ArrayList返回
    List<String> ret = list.subList(0, 4);
    System.out.println(ret);
    
    list.clear();
    System.out.println(list.size());
}

ArrayList的遍历

ArrayList 可以使用三方方式遍历:for循环+下标、for-each、使用迭代器

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);
    
    // 使用下标+for遍历
    for (int i = 0; i < list.size(); i++) {
    	System.out.print(list.get(i) + " ");
    } 
    System.out.println();
    
    // 借助foreach遍历
    for (Integer integer : list) {
    	System.out.print(integer + " ");
    } 
    System.out.println();
    //使用迭代器
    Iterator<Integer> it = list.listIterator();
    while(it.hasNext()){
    	System.out.print(it.next() + " ");
    } 
    System.out.println();
}

ArrayList的扩容机制

ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容:

不断的add,会频繁的扩容,扩容缺点

以下是ArrayList源码中扩容方式

Object[] elementData; // 存放元素的空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认空间
private static final int DEFAULT_CAPACITY = 10; // 默认容量大小
public boolean add(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    	return Math.max(DEFAULT_CAPACITY, minCapacity);
    } 
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    	grow(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // 获取旧空间大小
    int oldCapacity = elementData.length;
    // 预计按照1.5倍方式扩容
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
    if (newCapacity - minCapacity < 0)
   		newCapacity = minCapacity;
    // 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    	newCapacity = hugeCapacity(minCapacity);
    // 调用copyOf扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
    // 如果minCapacity小于0,抛出OutOfMemoryError异常
    if (minCapacity < 0)
    	throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

【总结】

1. 检测是否真的需要扩容,如果是调用grow准备扩容
2. 预估需要库容的大小
 	1. 初步预估按章1.5倍大小扩容
 	2. 如果用户所需大小超过1.5倍大小,则按照用户所需大小扩容
 	3. 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
3. 使用copyOf进行扩容那个

ArrayList的模拟实现

简单版

public class SeqList {
    // 打印顺序表
    public void toString() { }
    // 新增元素,默认在数组最后新增
    public void add(int data) { }
    // 在 pos 位置新增元素
    public void add(int pos, int data) { }
    // 判定是否包含某个元素
    public boolean contains(int toFind) { return true; }
    // 查找某个元素对应的位置
    public int indexOf(int toFind) { return -1; }
    // 获取 pos 位置的元素
    public int get(int pos) { return -1; }
    // 给 pos 位置的元素设为 value
    public void set(int pos, int value) { }
    //删除第一次出现的关键字key
    public void remove(int toRemove) { }
    // 获取顺序表长度
    public int size() { return 0; }
    // 清空顺序表
    public void clear() { }
}

代码实现

import java.util.Arrays;

/**
 * Created with IntelliJ IDEA
 * Description:
 * User: 16037
 * Date: 2022-03-12
 * Time:9:38
 */
public class MyArrayList {
    public int[] elem;//null
    public int usedSize;//0

    public MyArrayList() {
        this.elem = new int[5];//默认初始容量为5
    }


    // 新增元素,默认在数组最后新增
    public void add(int data) {
        //1、判断是不是满的
        if(isFull()) {
            //扩容
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }
        this.elem[this.usedSize] = data;
        this.usedSize++;
    }
    //容量是否已满
    public boolean isFull() {
        if(this.usedSize == this.elem.length) {
            return true;//满的
        }
        return false;
    }
    // 打印顺序表
    public void myToString() {
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(this.elem[i]+" ");
        }
        System.out.println();
    }

    // 在 pos 位置新增元素
    public void add(int pos, int data) {
        if(isFull()) {
            this.elem = Arrays.copyOf(this.elem,this.usedSize*2);
        }
        //2、正常的插入
        if(pos > this.usedSize || pos < 0)
            return;

        for (int i = this.usedSize; i > pos ; i--) {
            this.elem[i] = this.elem[i-1];
        }
        this.elem[pos] = data;
        this.usedSize++;
    }
    // 判定是否包含某个元素
    public boolean contains(int toFind) {
        for(Integer x : this.elem) {
            if (x == toFind) {
                return true;
            }
        }
        return false;
    }
    // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if(this.elem[i] == toFind) {
                return i;
            }
        }
        return -1;
    }
    // 获取 pos 位置的元素
    public int get(int pos) {
        if(pos<0||pos>=this.usedSize)
            //一般为自定义异常,这里只是检查一下
            return -1;
        return this.elem[pos];
    }
    // 给 pos 位置的元素设为 value
    public void set(int pos, int value) {
        if(pos<0||pos>=this.usedSize){
            System.out.println("pos不合法!");
            throw new RuntimeException("pos不合法!");
        }
        this.elem[pos]=value;
    }
    //删除第一次出现的关键字key
    //若元素为引用类型,要将最后一个位置置空,才能被Java垃圾回收装置回收,清空则要遍历置空每一位置
    public void remove(int toRemove) {
        if(isEmpty()){
            return;
        }
        int index = indexOf(toRemove);
        if(index == -1){
            System.out.println("无此关键字!");
            return ;
        }
        for(int i=index;i<this.usedSize-1;i++){
            this.elem[i]=this.elem[i+1];
        }
        this.usedSize--;
    }
    //顺序表不为空
    public boolean isEmpty(){
        if(this.usedSize==0)
            return true;
        return false;
    }

    // 获取顺序表长度
    public int size() {
        return this.usedSize;
    }
    // 清空顺序表
    public void clear() {
        this.usedSize=0;
    }

}

最难不过坚持!

你可能感兴趣的:(JAVA数据结构,笔记)