03-ArrayList(CustomDynamicArray)

数据结构是什么?

image-20230103151100206.

image-20230103151202629.

动态数组

image-20230103151344651.

image-20230103151410186.

image-20230103151434291.

Person.java

package com.rnny;

/**
 * @author 软柠柠吖(Runny)
 * @date 2023-01-03
 */
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Person - finalize");
    }
}

Assert.java

package com.rnny;

/**
 * @author 软柠柠吖(Runny)
 * @date 2023-01-01
 */
public class Assert {
    public static void test(boolean value) {
        try {
            // if (value) {} 与 if (!value) {}❓
            //  答:if (value):表示 value 为 true 时,执行 if 语句
            //      => 简单理解为:当 value 是真的时,执行 if 语句(额……有点多余)
            //      if (!value):表示 value 为 false 时,执行 if 语句
            //      => 简单理解为:当 value 为假的时,执行 if 语句。
            // 总结:value => true ==> if (value) {}
            //      value => false ==> if (!value) {}
            if (!value) {
                throw new Exception("测试未通过❗");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ArrayList.java

ArrayList<E>

image-20230103151726785.

package com.rnny;

/**
 * @author 软柠柠吖(Runny)
 * @date 2023-01-01
 */
@SuppressWarnings("unchecked")
public class ArrayList<E> {
}

成员变量

/**
 * 元素的数量
 */
private int size;
/**
 * 所有的元素
 */
private E[] elements;
/**
 * 默认容量 <br>
 * static:保证变量的内存空间仅此一份
 */
private static final int DEFAULT_CAPACITY = 16;
/**
 * 元素未发现
 */
private static final int ELEMENT_NOT_FOUND = -1;

构造函数

image-20230103151747948.

public ArrayList() {
    // this(参数):调用有参构造
    this(DEFAULT_CAPACITY);
}

public ArrayList(int capacity) {
    // > VS <(大于号与小于号如何区分❓)
    //  答:从左向右看(正常看的顺序),如果为大头,则为大于号 '>'
    //                              如果为小头,则为小于号 '<'

    // 等价于:capacity = Math.max(capacity, DEFAULT_CAPACITY);
    capacity = capacity < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capacity;
    this.size = 0;
    // 对象数组中每个位置存储的是什么❓
    //  答:地址❗对象数组中存储的是对象的地址,而不是具体的对象。
    //  如果存储的是具体的对象,那 Object 就没法用了,这样设计也不好
    //  因为具体对象的大小不确定,无法用 Object[] 接收。
    //  存储地址就能完美的解决这个问题🐲
    this.elements = (E[]) new Object[capacity];
}

clear()

image-20230103151803561.

/**
 * 清除所有元素
 */
public void clear() {
    for (int i = 0; i < size; i++) {
        elements[i] = null;
    }
    size = 0;
}

size()

/**
 * 元素的数量
 *
 * @return
 */
public int size() {
    return size;
}

isEmpty()

/**
 * 是否为空
 *
 * @return
 */
public boolean isEmpty() {
    // 这里为什么要写 "size == 0" ?
    //  答:自己不应该纠结 JAVA 的判断语句怎么写,而是要按照具体的逻辑写代码。
    //      说的有点抽象❓ 一言以蔽之:用具体的代码来实现方法名所表达的含义❗
    //      例如,方法名 isEmpty:是空的 ==> 则具体的代码就写判断为空即可 ==> 即代码为:"size == 0"
    return size == 0;
}

contains(E element)

/**
 * 是否包含某个元素
 *
 * @param element
 * @return
 */
public boolean contains(E element) {
    return indexOf(element) != ELEMENT_NOT_FOUND;
}

add(E element)

image-20230103151508403.

/**
 * 添加元素到最后面
 *
 * @param element
 */
public void add(E element) {
    // 代码重用
    add(size, element);
    //if (size >= this.elements.length) {
    //    resize();
    //}
    //this.elements[size++] = element;
}

add(int index, E element)

image-20230103151643858.

/**
 * 往 index 位置添加元素
 *
 * @param index
 * @param element
 */
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacity(size + 1);
    //if (size >= this.elements.length) {
    //    resize();
    //}
    //for (int i = size - 1; i >= index; i--) {
    //    elements[i + 1] = elements[i];
    //}
    for (int i = size; i > index; i--) {
        elements[i] = elements[i - 1];
    }
    this.elements[index] = element;
    size++;
}

rangeCheckForAdd(int index)

/**
 * 添加元素时检查下表是否越界
 * @param index
 */
private void rangeCheckForAdd(int index) {
    if (index < 0 || index > size) {
        outOfBounds(index);
    }
}

resize()

最终结果没有使用此方法

private void resize() {
    E[] newArray = (E[]) new Object[elements.length * 2];
    System.arraycopy(elements, 0, newArray, 0, elements.length);
    elements = newArray;
}

ensureCapacity(int desiredCapacity)

image-20230103151706968.

/**
 * 保证有 desiredCapacity 的容量
 *
 * @param desiredCapacity
 */
private void ensureCapacity(int desiredCapacity) {
    // “微观” VS “宏观”❓
    //  答:微观上指具体的数组元素(索引下标),即水桶中水的位置(刻度线)
    //     宏观上指数组的容量,即水桶的大小(容积)
    // ensureCapacity:保证数组有地方存储增加的元素,因此这里是 “宏观” 问题,即判断容量是否足够。
    int oldCapacity = elements.length;
    if (oldCapacity >= desiredCapacity) {
        return;
    }
    // 新容量为旧容量的 1.5 倍
    //  注意:位移运算符 >> 要和 () 一同使用,既提高了优先级,又不出错,容易阅读
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    E[] newElements = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newElements[i] = elements[i];
    }
    elements = newElements;
    System.out.println(oldCapacity + " 扩容为 " + newCapacity);
}

get(int index)

/**
 * 返回 index 位置对应的元素
 *
 * @param index
 * @return
 */
public E get(int index) {
    rangeCheck(index);
    return elements[index];
}

rangeCheck(int index)

/**
 * 检查下标是否越界(查询或删除元素)
 * @param index
 */
private void rangeCheck(int index) {
    if (index < 0 || index >= size) {
        outOfBounds(index);
    }
}

set(int index, E element)

/**
 * 设置 index 位置的元素
 *
 * @param index
 * @param element
 * @return
 */
public E set(int index, E element) {
    E oldValue = get(index);
    this.elements[index] = element;
    return oldValue;
}

outOfBounds(int index)

private void outOfBounds(int index) {
    throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}

remove(int index)

image-20230103151616424.

image-20230103151823370.

/**
 * 删除 index 位置对应的元素
 *
 * @param index
 * @return
 */
public E remove(int index) {
    E oldValue = get(index);
    for (int i = index + 1; i < size; i++) {
        this.elements[i - 1] = this.elements[i];
    }
    this.elements[--size] = null;
    return oldValue;
}

remove(E element)

/**
 * 删除与 element 相等的元素
 * @param element
 * @return
 */
public E remove(E element) {
    return remove(indexOf(element));
}

indexOf(E element)

image-20230103151844234.

/**
 * 查看元素的位置
 *
 * @param element
 * @return
 */
public int indexOf(E element) {
    // for 循环的 "i < size" 判断条件如何理解❓(注意:忽略 for 循环花括号里的语句)
    //  答:① 按照 “指针” 思想来理解,将 i 看作指向每个数组元素的 “索引指针”❗
    //  即,“索引指针” 从 0 开始(第一个元素)遍历,直到 size - 1(最后一个元素)结束。
    //      ② 按照 “执行次数 ” 来理解,这里符合 “等 0 小数”(i = 0, i < 执行次数)的规则,
    //  那么 size 即为执行次数❗
    //  即,for 循环执行 size 次,数组中每个元素都有被遍历到的机会。
    if (element == null) {
        for (int i = 0; i < size; i++) {
            if (this.elements[i] == null) {
                return i;
            }
        }
    } else {
        for (int i = 0; i < size; i++) {
            if (element.equals(this.elements[i])) {
                return i;
            }
        }
    }

    //for (int i = 0; i < size; i++) {
    //    if (this.elements[i] == null) {
    //        if (element == null) {
    //            return i;
    //        }
    //    } else if (this.elements[i].equals(element)) {
    //        return i;
    //    }
    //}
    return ELEMENT_NOT_FOUND;
}

toString()

image-20230103151531099.

@Override
public String toString() {
    // 格式:size=3, [55, 22, 2]
    // 字符串拼接推荐使用:StringBuilder
    StringBuilder sb = new StringBuilder();
    sb.append("size=").append(size).append(", [");
    for (int i = 0; i < size; i++) {
        // 不需要减法运算,【推荐】
        if (i != 0) {
            sb.append(", ").append(elements[i]);
        } else {
            sb.append(elements[i]);
        }
        // 不是很好❓
        //  原因:判断的时候需要 size - 1,进行减法运算,效率不高
        //if (i == size - 1) {
        //    sb.append(elements[i]).append("]");
        //} else {
        //    sb.append(elements[i]).append(", ");
        //}
    }
    sb.append("]");
    return sb.toString();
}

完整版

image-20230103150409705.

package com.rnny;

/**
 * @author 软柠柠吖(Runny)
 * @date 2023-01-01
 */
@SuppressWarnings("unchecked")
public class ArrayList<E> {

    /**
     * 元素的数量
     */
    private int size;
    /**
     * 所有的元素
     */
    private E[] elements;
    /**
     * 默认容量 <br>
     * static:保证变量的内存空间仅此一份
     */
    private static final int DEFAULT_CAPACITY = 16;
    /**
     * 元素未发现
     */
    private static final int ELEMENT_NOT_FOUND = -1;

    public ArrayList() {
        // this(参数):调用有参构造
        this(DEFAULT_CAPACITY);
    }

    public ArrayList(int capacity) {
        // > VS <(大于号与小于号如何区分❓)
        //  答:从左向右看(正常看的顺序),如果为大头,则为大于号 '>'
        //                              如果为小头,则为小于号 '<'

        // 等价于:capacity = Math.max(capacity, DEFAULT_CAPACITY);
        capacity = capacity < DEFAULT_CAPACITY ? DEFAULT_CAPACITY : capacity;
        this.size = 0;
        // 对象数组中每个位置存储的是什么❓
        //  答:地址❗对象数组中存储的是对象的地址,而不是具体的对象。
        //  如果存储的是具体的对象,那 Object 就没法用了,这样设计也不好
        //  因为具体对象的大小不确定,无法用 Object[] 接收。
        //  存储地址就能完美的解决这个问题🐲
        this.elements = (E[]) new Object[capacity];
    }

    /**
     * 清除所有元素
     */
    public void clear() {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;
    }

    /**
     * 元素的数量
     *
     * @return
     */
    public int size() {
        return size;
    }

    /**
     * 是否为空
     *
     * @return
     */
    public boolean isEmpty() {
        // 这里为什么要写 "size == 0" ?
        //  答:自己不应该纠结 JAVA 的判断语句怎么写,而是要按照具体的逻辑写代码。
        //      说的有点抽象❓ 一言以蔽之:用具体的代码来实现方法名所表达的含义❗
        //      例如,方法名 isEmpty:是空的 ==> 则具体的代码就写判断为空即可 ==> 即代码为:"size == 0"
        return size == 0;
    }

    /**
     * 是否包含某个元素
     *
     * @param element
     * @return
     */
    public boolean contains(E element) {
        return indexOf(element) != ELEMENT_NOT_FOUND;
    }

    /**
     * 添加元素到最后面
     *
     * @param element
     */
    public void add(E element) {
        add(size, element);
        //if (size >= this.elements.length) {
        //    resize();
        //}
        //this.elements[size++] = element;
    }

    private void resize() {
        E[] newArray = (E[]) new Object[elements.length * 2];
        System.arraycopy(elements, 0, newArray, 0, elements.length);
        elements = newArray;
    }

    /**
     * 保证有 desiredCapacity 的容量
     *
     * @param desiredCapacity
     */
    private void ensureCapacity(int desiredCapacity) {
        // “微观” VS “宏观”❓
        //  答:微观上指具体的数组元素(索引下标),即水桶中水的位置(刻度线)
        //     宏观上指数组的容量,即水桶的大小(容积)
        // ensureCapacity:保证数组有地方存储增加的元素,因此这里是 “宏观” 问题,即判断容量是否足够。
        int oldCapacity = elements.length;
        if (oldCapacity >= desiredCapacity) {
            return;
        }
        // 新容量为旧容量的 1.5 倍
        //  注意:位移运算符 >> 要和 () 一同使用,既提高了优先级,又不出错,容易阅读
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        E[] newElements = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newElements[i] = elements[i];
        }
        elements = newElements;
        System.out.println(oldCapacity + " 扩容为 " + newCapacity);
    }

    /**
     * 返回 index 位置对应的元素
     *
     * @param index
     * @return
     */
    public E get(int index) {
        rangeCheck(index);
        return elements[index];
    }

    /**
     * 检查下标是否越界(查询或删除元素)
     * @param index
     */
    private void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            outOfBounds(index);
        }
    }

    /**
     * 设置 index 位置的元素
     *
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element) {
        E oldValue = get(index);
        this.elements[index] = element;
        return oldValue;
    }

    /**
     * 往 index 位置添加元素
     *
     * @param index
     * @param element
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacity(size + 1);
        //if (size >= this.elements.length) {
        //    resize();
        //}
        //for (int i = size - 1; i >= index; i--) {
        //    elements[i + 1] = elements[i];
        //}
        for (int i = size; i > index; i--) {
            elements[i] = elements[i - 1];
        }
        this.elements[index] = element;
        size++;
    }

    /**
     * 添加元素时检查下表是否越界
     * @param index
     */
    private void rangeCheckForAdd(int index) {
        if (index < 0 || index > size) {
            outOfBounds(index);
        }
    }

    private void outOfBounds(int index) {
        throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }

    /**
     * 删除 index 位置对应的元素
     *
     * @param index
     * @return
     */
    public E remove(int index) {
        E oldValue = get(index);
        for (int i = index + 1; i < size; i++) {
            this.elements[i - 1] = this.elements[i];
        }
        this.elements[--size] = null;
        return oldValue;
    }

    /**
     * 删除与 element 相等的元素
     * @param element
     * @return
     */
    public E remove(E element) {
        return remove(indexOf(element));
    }

    /**
     * 查看元素的位置
     *
     * @param element
     * @return
     */
    public int indexOf(E element) {
        // for 循环的 "i < size" 判断条件如何理解❓(注意:忽略 for 循环花括号里的语句)
        //  答:① 按照 “指针” 思想来理解,将 i 看作指向每个数组元素的 “索引指针”❗
        //  即,“索引指针” 从 0 开始(第一个元素)遍历,直到 size - 1(最后一个元素)结束。
        //      ② 按照 “执行次数 ” 来理解,这里符合 “等 0 小数”(i = 0, i < 执行次数)的规则,
        //  那么 size 即为执行次数❗
        //  即,for 循环执行 size 次,数组中每个元素都有被遍历到的机会。
        if (element == null) {
            for (int i = 0; i < size; i++) {
                if (this.elements[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (element.equals(this.elements[i])) {
                    return i;
                }
            }
        }

        //for (int i = 0; i < size; i++) {
        //    if (this.elements[i] == null) {
        //        if (element == null) {
        //            return i;
        //        }
        //    } else if (this.elements[i].equals(element)) {
        //        return i;
        //    }
        //}
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        // 格式:size=3, [55, 22, 2]
        // 字符串拼接推荐使用:StringBuilder
        StringBuilder sb = new StringBuilder();
        sb.append("size=").append(size).append(", [");
        for (int i = 0; i < size; i++) {
            // 不需要减法运算,【推荐】
            if (i != 0) {
                sb.append(", ").append(elements[i]);
            } else {
                sb.append(elements[i]);
            }
            // 不是很好❓
            //  原因:判断的时候需要 size - 1,进行减法运算,效率不高
            //if (i == size - 1) {
            //    sb.append(elements[i]).append("]");
            //} else {
            //    sb.append(elements[i]).append(", ");
            //}
        }
        sb.append("]");
        return sb.toString();
    }
}

Main.java

package com.rnny;

/**
 * @author 软柠柠吖(Runny)
 * @date 2023-01-01
 */
public class Main {
    public static void main(String[] args) {
        // 所有的类,最终都继承 java.lang.Object

        // new:向堆空间申请内存
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        //list.add(list.size() - 1, 100);
        //list.set(0, 30);
        //list.remove(2);
        //System.out.println(list);
        //Assert.test(list.get(list.size() - 2) == 100);

        ArrayList<Person> personArrayList = new ArrayList<>();
        personArrayList.add(new Person("jack", 12));
        personArrayList.add(new Person("tom", 14));
        personArrayList.add(new Person("scott", 16));

        personArrayList.clear();
        // 提醒 JVM 进行垃圾回收
        System.gc();
    }
}
posted @ 2023-01-03 15:21  软柠柠吖  阅读(12)  评论(0编辑  收藏  举报