2020-05-15

项目优化和List集合

1、项目优化

1.1 分析当前情况

问题

    数据存储是数组形式,数据类型明确。复用度较低。

需求

    Student操作使用的代码,StudentManager想要操作考虑一个复用度问题。不管更换什么数据类型, 都是可以直接使用。

解决问题

    1. 泛型

    2. 数组不能使用泛型,但是这个数组又需要保存各式各样的数据

        Object

1.2 使用泛型和Object优化项目

package com.qfedu.student.system.util;

import com.qfedu.student.system.myexception.IllegalCapacityException;

import com.qfedu.student.system.myexception.OverflowMaxArraySizeException;

/**

* 自定义数据存储工具,MyList用于存储代码中操作的数据

*

* @author Anonymous

*

* @param <E> 使用泛型满足更多的情况

*/

public class MyList<E> {

    /**

    * 保存数据的底层Object数组,可以保存任意数据类型,但是在操作方法是会

    * 对操作的数据类型,通过泛型进行约束操作

    */

    private Object[] elementData = null;

    /**

    * DEFAULT_CAPACITY 默认容量,这里是一个带有名字的常量

    */

    private static final int DEFAULT_CAPACITY = 10;

    /**

    * 数组最大容量,是int类型最大值 - 8 

    */

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

    /**

    * 当前底层Object数组中有效元素个数

    */

    private int size = 0;

    /**

    * 无参数构造方法,使用DEFAULT_CAPACITY约束初始化容量

    */

    public MyList() {

        elementData = new Object[DEFAULT_CAPACITY];

    }

    /**

    * 提供给用户可以指定初始化容量的操作方法

    *

    * @param initCapacity 用户指定的初始化容量,但是必须在合理范围以内

    * @throws IllegalCapacityException 用户指定的初始化容量超出范围

    */

    public MyList(int initCapacity) throws IllegalCapacityException {

        if (initCapacity < 0 || initCapacity > MAX_ARRAY_SIZE) {

            throw new IllegalCapacityException("Input Parameter is Invalid!");

        }

        elementData = new Object[initCapacity];

    }

    /**

    * 添加方法,添加在创建MyList对象是约束的具体数据类型

    *

    * @param e 通过泛型约束的具体数据类型

    * @return 添加成功返回true, 否则返回false

    * @throws OverflowMaxArraySizeException

    */

    public boolean add(E e) throws OverflowMaxArraySizeException {

        if (size == elementData.length) {

            // 添加操作是一个元素,最小容量要求就是在原本的数组容量之上 + 1

            grow(size + 1);

        }

        elementData[size] = e;

        size += 1;

        return true;

    }

    /**

    * 删除保存在MyList中的指定元素

    *

    * @param obj 用户指定的元素

    * @return 删除成功返回true,否则返回false

    */

    public boolean remove(Object obj) {

        int index = indexOf(obj);

        return remove(index);

    }

    /**

    * 删除指定下标的的元素

    *

    * @param index 用户指定的下标位置

    * @return 删除成功返回true,否则返回false

    */

    public boolean remove(int index) {

        if (index < 0 || index >= size) {

            return false;

        }

        for (int i = index; i < size - 1; i++) {

            elementData[i] = elementData[i + 1];

        }

        // 原本最后一个有效元素位置赋值为null

        elementData[size - 1] = null;

        // 有效元素个数 - 1

        size -= 1;

        return true;

    }

    /**

    * 根据指定元素,找出对应的下标位置,没有找到返回-1

    *

    * @param obj 用户传入的元素

    * @return 找到元素返回值大于等于0,没有找到返回-1

    */

    public int indexOf(Object obj) {

        int index = -1;

        for (int i = 0; i < size; i++) {

            if (obj.equals(elementData[i])) {

                index = i;

                break;

            }

        }

        return index;

    }

/**

* 替换修改方法,使用指定元素替换指定下标的元素

*

* @param index 指定的下标位置,约束在合理范围以内

* @param e 泛型约束的指定数据类型,保证数据类型一致化

* @return 被替换掉的元素。如果没有被替换的元素,返回null

*/

    public E set(int index, E e) {

        if (index < 0 || index >= size) {

            return null;

        }

        // 取出原本的元素

        E temp = (E) elementData[index];

        elementData[index] = e;

       return temp;

    }

    /**

    * 返回当前MyList底层数组中保存有效元素个数是多少个

    *

    * @return 返回当前MyList底层数组中有效元素个数

    */

    public int size() {

        return size;

    }

    /**

    * 判断当前MyList中是否为空

    *

    * @return 如果是空返回true,否则返回false

    */

    public boolean isEmpty() {

        return 0 == size;

    }

    /**

    * 判断指定元素是否在MyList底层数组中存在

    *

    * @param obj 用户指定的元素

    * @return 存在返回true,不存在返回false

    */

    public boolean contains(Object obj) {

        return indexOf(obj) > -1;

    }

    /**

    * 根据指定下标位置,获取对应的元素

    *

    * @param index 指定下标位置

    * @return 对应的元素,如果不存在,返回null

    */

    public E get(int index) {

        return index > -1 && index < size ? (E) elementData[index] : null;

    }

    /**

    * 类内私有化方法,用于在添加元素过程中,出现当前底层数组容量不足的情况下 对底层数组进行扩容操作,满足使用要求

    *

    * @param minCapacity 添加操作要求的最小容量

    * @throws OverflowMaxArraySizeException 数组容量超出最大范围

    */

    private void grow(int minCapacity) throws OverflowMaxArraySizeException {

        // 1. 获取原数组容量

        int oldCapacity = elementData.length;

        // 2. 计算得到新数组容量,新数组容量大约是原数组容量的1.5倍

        // >> 1 右移一位 该操作是二进制操作 等价于 / 2 效率略高

        int newCapacity = oldCapacity + (oldCapacity >> 1);

       // 3. 判断新数组容量是否满足最小容量要求

       if (minCapacity > newCapacity) {

           newCapacity = minCapacity;

        }

        // 4. 判断当前容量是否超出了MAX_ARRAY_SIZE

       if (newCapacity > MAX_ARRAY_SIZE) {

            throw new OverflowMaxArraySizeException("Overflow MAX_ARRAY_SIZE");

        }

        // 5. 创建新数组

        Object[] temp = new Object[newCapacity];

        // 6. 数据拷贝

        for (int i = 0; i < oldCapacity; i++) {

            temp[i] = elementData[i];

        }

        // 7. 使用allStus保存新数组首地址

        elementData = temp;

    }

}

2、List接口

2.1 List接口概述

List接口特征:

    1. 数据存储可重复。

    2. 有序,添加顺序和保存顺序一致。

--| ArrayList<E>

    可变长数组

--| LinkedList<E>

    双向链表

--| Vector<E>

    线程安全的可变长数组

2.2 List常用方法

    boolean add(E e);

        List接口继承Collection接口 add方法,使用操作和Collection一致,并且这里采用的添加方式是【尾插法】

    boolean add(int index, E e);

        List接口【特有方法】,在指定位置,添加指定元素。

    boolean addAll(Collection<? extends E> c);

        List接口继承Collection接口 addAll方法,使用操作和Collection一致,并且这里采用的添加方式是【尾插法】

    boolean addAll(int index, Collection<? extends E> c);

        List接口【特有方法】,在指定下标位置,添加另一个集合中所有内容

    E remove(int index);

        List接口【特有方法】,获取指定下标位置的元素。

    boolean remove(Object obj);

        List接口继承Collection接口方法。删除集合中的指定元素

    boolean removeAll(Collection<?> c);

        List接口继承Collection接口方法。删除当前集合中和参数集合重复元素

    boolean retainAll(Collection<?> c);

        List接口继承Collection接口方法。保留当前集合中和参数集合重复元素

    clear();

        List接口继承Collection接口方法。清空整个集合中的所有元素

    E set(int index, E e);

        List接口【特有方法】,使用指定元素替代指定下标的元素,返回值是被替换的元素

    int size();

        List接口继承Collection接口方法。获取集合中有效元素个数

    boolean isEmpty();

        List接口继承Collection接口方法。判断当前集合是否为空

    boolean contains(Object obj);

        List接口继承Collection接口方法。判断指定元素是否包含在当前集合中

    boolean containsAll(Collection<?> c);

        List接口继承Collection接口方法。判断参数集合是不是当前集合在子集合

    Object[] toArray();

        List接口继承Collection接口方法。获取当前集合中所有元素Object数组

    E get(int index);

        List接口【特有方法】。获取指定下标对应的元素

    List<E> subList(int fromIndex, int toIndex);

        List接口【特有方法】。获取当前集合指定子集合,从fromIndex开始,到toIndex结束。fromIndex <= 范围 < toIndex

    int indexOf(Object obj);

        List接口【特有方法】。获取指定元素在集合中第一次出现位置

    int lastIndexOf(Object obj);

        List接口【特有方法】。获取指定元素在集合中最后一次出现的位置

2.3 List接口常用方法演示

package com.qfedu.a_list;

import java.util.ArrayList;

import java.util.List;

public class Demo1 {

    public static void main(String[] args) {

        /*

        * List<E>是一个接口,没有自己的类对象,这里使用List接口的

        * 实现类ArrayList来演示代码。

        */

        List<String> list = new ArrayList<String>();

        /*

        * 添加方法演示

        */

        list.add("浓郁咖啡摩卡");

        list.add("浓郁咖啡拿铁");

        list.add("焦糖玛奇朵");

        list.add("摩卡可可碎星冰乐");

        list.add("可可卡布奇诺");

        System.out.println(list);

        list.add(3, "美式咖啡");

        System.out.println(list);

        List<String> list2 = new ArrayList<String>();

        list2.add("肥宅快乐水");

        list2.add("芬达");

        list2.add("雪碧");

        list2.add("冰峰");

        list.addAll(4, list2);

        System.out.println(list);

        /*

        * 删除方法

        */

        String remove = list.remove(1);

        System.out.println(remove);

        System.out.println(list);

        // 条件过滤,这里使用了JDK1.8 新特征 Lambda表达式和函数式接口 【后期知识点】

        list.removeIf((str) -> str.length() > 4);

        System.out.println(list);

    }

}

package com.qfedu.a_list;

import java.util.ArrayList;

import java.util.List;

public class Demo1 {

    public static void main(String[] args) {

        /*

        * List<E>是一个接口,没有自己的类对象,这里使用List接口的

        * 实现类ArrayList来演示代码。

        */

        List<String> list = new ArrayList<String>();

        /*

        * 添加方法演示

        */

        list.add("浓郁咖啡摩卡");

        list.add("浓郁咖啡拿铁");

        list.add("焦糖玛奇朵");

        list.add("摩卡可可碎星冰乐");

        list.add("可可卡布奇诺");

        System.out.println(list);

        list.add(3, "美式咖啡");

        System.out.println(list);

        List<String> list2 = new ArrayList<String>();

        list2.add("肥宅快乐水");

        list2.add("芬达");

        list2.add("雪碧");

        list2.add("冰峰");

        list.addAll(4, list2);

        System.out.println(list);

        /*

        * 删除方法

        */

        String remove = list.remove(1);

        System.out.println(remove);

        System.out.println(list);

        // 条件过滤,这里使用了JDK1.8 新特征 Lambda表达式和函数式接口 【后期知识点】

        list.removeIf((str) -> str.length() > 4);

        System.out.println(list);

    }

}

3. ArrayList【重点】

3.1 ArrayList概述

ArrayList是在Java中集合非常重要的一个组装,基于数组完成的数据结构。可变长数组操作!!!

底层保存数据的是一个Object类型数组。

ArrayList使用的方法都是List接口中的方法,有两个需要了解的成员方法:

    ensureCapacity();

        判断方法,用于确定当前底层数组的容量是否满足当前操作的需求。

    trimToSize();

        节省空间,将底层数组的容量缩容至有效元素个数

需要掌握的是关于ArrayList效率相关的问题。和细节问题

3.2 细节问题

1. DEFAULT_CAPACITY

    默认容量 private static final int DEFAULT_CAPACITY = 10;

    在调用ArrayList无参数构造方法是,才会使用DEFAULT_CAPACITY,作为底层Object数组的初始化容量。如果用户指定调用的是带有初始化底层Object数组容量的构造方法,会根据用户指定的容量创建对一个ArrayList集合。】

2. MAX_ARRAY_SIZE ==> Integer.MAX_VALUE - 8;

    int[] arr = new int[10];

    arr.length 是什么??? 数组容量

    这里是一个方法还是属性??? 属性

    属性是不是成员变量??? 是

    成员变量是否需要占用内存??? 需要

    new数组占用的空间什么地方??? 堆区

    arr.length 属性是不是也在堆区??? 是

    为什么 - 8???

    因为在数组中存在很多属性,length只是众多属性中的一个,在创建数组使用的过程中,需要留有内存空间用于保存数组中属性。

3.3 效率问题

ArrayList特征:

    增删慢

        增加慢

            1. 数组当前容量无法满足添加操作,需要进行grow扩容方法执行,在扩容方法中,存在数组创建,数组数据拷贝。非常浪费时间,而且浪费内存。

            2. 数组在添加数据的过程中,存在在指定位置添加元素,从指定位置开始,之后的元素整体向后移动。

        删除慢

            1. 删除数据之后,从删除位置开始,之后的元素整体向前移动,移动过程非常浪费时间

            2. 删除操作会导致数据空间的浪费,内存的浪费

    查询快

        ArrayList底层是一个数组结构,在查询操作的过程中,是按照数组+下标的方式来操作对应的元素,数组+下标方式可以直接获取对应的空间首地址,CPU访问效率极高。

3.4 【补充知识点,内存地址】

内存地址概念:

    [计算机原理]

    计算机中为了更好的使用内存,操作程序,完成代码。将内存按照最小单位,进行编号处理。

    最小单位: 字节 byte

    从编号为0内存开始,到内存的最大值。地址的展示方式是十六进制。

3.5 【补充知识点 内存地址对于CPU有什么关系】

代码实际运行:

    CPU就是根据内存地址,可以直达内存所在区域,执行对应代码。精准而优雅,速度非常快!!!

3.6 【补充知识点 数组空间地址关系】


3.7【补充知识点 null到底是什么】

null 是计算机中非常特殊的一块内存。该内存编号 0x0000 0000

该内存受到系统保护

    不只是电脑,包括手机,iPad,智能设备,只要存在计算机基本结构的设备上都存在null 编号为0x0内存。大小一个字节该内存不能读取任何数据,也不能写入任何数据。一旦操作,程序直接被系统杀死

    Kill -9

    一般用于引用数据类型的初始化,利用开发中关于null的异常,辅助找出代码中的错误。

    NullPointerException.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350