Java集合框架(List)

1、为什么使用集合?

1)数组的缺陷:定容[一旦数组定义好,数组的长度就无法改变],如果需要改变数组的长度,很复杂。

2)定义可变长度的容器。(自己手撕一个可变长的容器)

public class MyAry {
    //定义一个object类型的数组
    private Object[] arr;
    //初始值为0,表示数组为空
    private int size;

    //无参构造,如果没有指定数组长度,默认调用本类的有参构造,指定初始值
    public MyAry() {
        this(3);
    }
    //有参构造,传入初始数组的长度
    public MyAry(int initSize){
        //判断传入参数的合法性
        if (initSize < 0){
            //手动抛出异常
            throw new RuntimeException("数组定义不合法");
        }
        //给arr赋值(存储的是数组地址)
        arr = new Object[initSize];
    }
    //向数组中添加元素
    public void setData(Object obj){
        //判断数组是否已满
        if (size >= arr.length){
            //创建新的数组,新数组长度为旧数组的2倍
            Object[] newArr = Arrays.copyOf(arr, size * 2);
            //改变arr中存储的内存地址,原来的旧数组会被GC(Garbage Collection)垃圾回收
            arr = newArr;
        }
        //将数据添加到arr中
        arr[size] = obj;
        size++;
    }
    //获取指定位置的数据
    public Object getData(int index){
        //判断传入参数的合法性,是否下标越界
        if (index >= arr.length){
            throw new ArrayIndexOutOfBoundsException("下标越界");
        }
        //返回Object类型的数据
        Object obj = arr[index];
        return obj;
    }
}

Java官网基于数组,根据不同的数据结构创建了多个类,这些类统称为集合框架。

集合框架图:

 2、List集合

List是一个接口,ArrayList和LinkedList是List接口的实现类

1)ArrayList集合的基本操作

public class TestArrayList {
    public static void main(String[] args) {
        //创建一个ArrayList对象
        List list = new ArrayList();//默认初始值为10

        //集合添加元素 element为Object类型
        list.add("aaa");
        list.add(1);
        list.add(new Date());
        list.add(2.34);
        System.out.println(list);

        //在指定下标位置添加元素
        list.add(2,"bbb");
        System.out.println(list);

        List list1 = new ArrayList();
        list1.add("a");
        list1.add("b");
        //在集合尾部添加其他的集合
        list.addAll(list1);
        System.out.println(list);

        //删除操作
        list.remove(1);
        System.out.println(list);

        //清空集合
        list1.clear();
        System.out.println(list1);

        //修改指定位置的数据
        list.set(1,"ccc");
        System.out.println(list);

        //查询操作
        //获取指定位置的元素
        Object o = list.get(1);
        System.out.println(o);
        //集合中是否包含具体元素,返回值为Boolean类型
        boolean gjx = list.contains("aaa");
        System.out.println(gjx);
        //获取指定元素在集合中第一次出现的下标位置
        int index = list.indexOf("aaa");
        System.out.println(index);
        //获取指定元素最后一次出现的下标位置
        int index1 = list.lastIndexOf("a");
        System.out.println(index1);
        //获取集合中的元素个数
        int size = list.size();
        System.out.println(size);
        //普通的for循环输出list
        for (int i = 0; i < size; i++) {
            System.out.println(list.get(i));
        }
        System.out.println("=========================");
        //for each 增强循环输出list
        for (Object object : list) {
            System.out.println(object);
        }
    }
}

2)相应源码

创建ArrayList对象

private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//无参构造
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//有参构造,有初始长度
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}

//有参构造,有初始内容
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

添加操作

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

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

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

删除操作

    //返回当前位置的值,并删除该位置的元素
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
    
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

修改操作

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

查询操作

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    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 int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    //集合中元素的个数
    public int size() {
        return size;
    }

3)LinkedList(链表结构)相关操作

public class TestLinkedList {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        //添加到尾部
        linkedList.add("java1");
        //添加到头部
        linkedList.addFirst("java0");
        //添加到尾部
        linkedList.addLast("java2");
        linkedList.add("java3");
        System.out.println(linkedList);

        //移除
        linkedList.removeFirst();
        System.out.println(linkedList);
        linkedList.remove(0);
        System.out.println(linkedList);

        //修改
        linkedList.set(1,"12");
        System.out.println(linkedList);

        //查询
        int size = linkedList.size();
        System.out.println(size);
        boolean empty = linkedList.isEmpty();
        System.out.println(empty);
        boolean java1 = linkedList.contains("java1");
        System.out.println(java1);
        Object obj = linkedList.get(0);
        System.out.println(obj);
        Object first = linkedList.getFirst();
        System.out.println(first);
        Object last = linkedList.getLast();
        System.out.println(last);
    }
}

4)相关源码

linkedList是一个双向链表结构,增加和删除效率比较高,不用像ArrayList那样移动元素。

linkedList的构造方法

//节点个数
transient int size = 0;

//头节点
transient Node<E> first;

//尾节点
transient Node<E> last;

//空构造方法
public LinkedList() {
}

//Node对象的定义
private static class Node<E> {
    //节点元素内容
    E item;
    //存储下一个节点的内存地址
    LinkedList.Node<E> next;
    //存储上一个节点的内存地址
    LinkedList.Node<E> prev;

    Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

LinkedList的添加方法

//添加方法
public boolean add(E e) {
    linkLast(e);
    return true;
}

//linkLast方法
void linkLast(E e) {
    //将最后一个节点赋值给l
    final LinkedList.Node<E> l = last;
    //创建一个新的节点对象,上一个节点指向l,元素为Object对象(e),下一个节点为空
    final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
    //让尾节点指向新创建的节点
    last = newNode;
    //如果当前对象的尾节点为空(即当前集合为空),让头节点也指向新建的节点对象
    if (l == null)
        first = newNode;
    else
        //如果尾节点不为空,就让当前元素的尾节点指向新创建的节点对象
        l.next = newNode;
    //节点个数加1
    size++;
    modCount++;
}

 添加元素的过程如下:

  1. 新创建的LinkedList对象中有三个属性值,first初始值为null,last初始值为null,size初始值为0;
  2. 添加第一个元素element1,此时last的值为null,调用linkLast方法,此时l的值为null;
  3. 在内存中新创建一个Node对象newNode,Node对象有三个属性,依次是prev,item,next;传入的值依次为l(null),element1,null;
  4. 让当前对象的尾节点指向element1节点;
  5. 如果l的值为空,即当前集合为空,就让当前对象的first节点指向element1节点,节点数量++;
  6. 添加第二个元素element2,此时last为element1节点,将last赋值给l,新创建一个Node对象,Node对象有三个属性,依次是prev,item,next;传入的值依次为l(element1节点),element2,null;
  7. 让当前对象的last节点指向element2节点,判断l的值是否为空,如果不为空,就让l的下一个节点指向element2节点;节点数量++;
  8. 此时的结构为first节点为element1节点,element1的prev为null,next为element2节点,element2的prev为element1节点,next为null,last节点为element2节点

LinkedList获取元素

public E get(int index) {
    //检查index下标是否正确
    checkElementIndex(index);
    //node对象有三个属性,分别为prev,item,next  item存储的是具体内容,其他两项存储的是其他node对象的地址
    return node(index).item;
}

//返回的值为node对象
LinkedList.Node<E> node(int index) {
    // assert isElementIndex(index);
    //“>>”:位运算二进制运算    size>>1的意思是一半也就相当于size/2
    //如果下标小于size/2
    if (index < (size >> 1)) {
        //从第一个节点从前向后查找
        LinkedList.Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {//如果下标大于size/2
        //从最后一个节点向前查找
        LinkedList.Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

LinkedList查询效率低,原因是因为需要一个节点一个节点的查找,没有存储下标,不能直接根据下标找到相应的元素。

总结

ArrayList的底层是数组结构,查询效率高,可以直接根据数组下标查询,但是增加和删除操作效率低,需要进行数据迁移。

LinkedList的底层是双向链表结构,增加和删除效率高,不需要移动数据,只需要改变其中节点的相应属性值即可,查询效率低,不能根据下标获取元素,需要一个一个查找。

3、Set集合

Set是一个接口,其中存储的内容无序,且不可重复。

1)HashSet集合的基本操作

创建HashSet对象(HashSet的底层为HashMap对象,默认容量为16,负载因子为0.75,当空间使用达到0.75时需要扩容)

public class TestHashSet {
    public static void main(String[] args) {
        //Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
        //HashSet的底层为HashMap对象,默认容量为16,负载因子为0.75,当空间使用达到0.75时需要扩容
        //也可以在创建对象的时候传入容量和负载因子
        HashSet hashSet = new HashSet();
        HashSet hashSet1 = new HashSet(16);
        //负载因子为float类型
        HashSet hashSet2 = new HashSet(16,0.7f);
    }
}

添加操作

        hashSet.add("java1");
        hashSet.add("java2");
        hashSet.add(1);
        hashSet.add(2);
        hashSet.add(5);
        hashSet.add(1);
        //hashSet中的元素不可重复,且无序
        System.out.println(hashSet);// [1, 2, java2, 5, java1]

删除操作

        hashSet.remove(5);
        System.out.println(hashSet);// [1, 2, java2, java1]

修改操作

hashSet没有set方法

 查询操作

        //查询hashSet中的元素个数
        int size = hashSet.size();
        System.out.println(size);// 4
        //清空hashSet中的元素
        hashSet.clear();
        System.out.println(hashSet);// []
        //判断hashSet是不是为空
        boolean empty = hashSet.isEmpty();
        System.out.println(empty);// true/false

hashSet集合的遍历

for each 遍历

        //hashSet的遍历
        // for each
        for (Object o : hashSet ) {
            System.out.println(o);// 1 2 java2 java1
        }

迭代器遍历

        // 迭代器遍历
        //1、获取迭代器对象,有序,有下标
        Iterator iterator = hashSet.iterator();
        //2、判断迭代器对象是否可以移动 hasNext的返回值为布尔类型
        while (iterator.hasNext()){
            //3、指定移动并获取当前元素
            Object obj = iterator.next();
            //4、输出当前元素
            System.out.println(obj);// 1 2 java2 java1
        }

2)TreeSet的基本操作

TreeSet中的方法和HashSet中的方法相同,TreeSet是对HashSet的排序,可以指定比较器,默认自然升序排序,如果存储自定义类型的数据,自定义类型需要实现实现Comparable接口 方可放入TreeSet,或者指定排序的对象。

TreeSet对象的创建

    //无参    
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }


    //传入指定的比较器
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

    //传入指定的初始集合
    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }


    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }

TreeSet集合的添加操作

String类型和Integer类型的数据添加

        TreeSet treeSet = new TreeSet();
        // TreeSet是对HashSet的排序,可以指定比较器,默认自然升序排序
        //存储String类型
        treeSet.add("java1");
        treeSet.add("java2");
        treeSet.add("java3");
        //treeSet.add(1);// java.lang.String cannot be cast to java.lang.Integer
        treeSet.add("java2");
        System.out.println(treeSet);// [java1, java2, java3]

        TreeSet treeSet1 = new TreeSet();
        //存储Integer类型
        treeSet1.add(1);
        treeSet1.add(4);
        treeSet1.add(2);
        System.out.println(treeSet1);// 1 2 4

自定义类型的数据添加

//自定义Student类型
public class Student {
    private String name;
    private Integer age;

    public Student() {
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class TestTreeSet {
    public static void main(String[] args) {

        TreeSet treeSet2 = new TreeSet();
        treeSet2.add(new Student("张三",19));
        treeSet2.add(new Student("李四",26));
        treeSet2.add(new Student("王五",21));
        System.out.println(treeSet2);
    }
}

此时控制台会报如下错误

查看源码发现,String包实现了Comparable接口,Integer类也实现了Comparable接口

 

解决方法:

1.TreeSet中的元素必须实现Comparable接口 方可放入TreeSet ,让自定义类型实现Comparable接口,并重写compareTo方法

public class Student implements Comparable{
    ……
    ……
    //排序:返回值如果大于0,表示当前元素比传入的o大,
    //     返回值如果小于0,表示当前元素比传入的o小
    //     返回值如果等于0,表示相同元素
    @Override
    public int compareTo(Object o) {
        Student student = (Student) o;
        if (this.age > student.age){
            return 1;
        }
        if (this.age < student.age){
            return -1;
        }
        return 0;
    }
}

public class TestTreeSet {
    public static void main(String[] args) {

        TreeSet treeSet2 = new TreeSet();
        treeSet2.add(new Student("张三",19));
        treeSet2.add(new Student("李四",26));
        treeSet2.add(new Student("王五",21));
        System.out.println(treeSet2);
        // [Student{name='张三', age=19}, Student{name='王五', age=21}, Student{name='李四', age=26}]
    }
}

2.在创建TreeSet时指定排序的对象。

新建一个选择器类型的对象,在创建TreeSet对象时传入参数

public class MyComparator implements Comparator {
    @Override
    public int compare(Object o1, Object o2) {
        Student student1 = (Student) o1;
        Student student2 = (Student) o2;
        if (student1.getAge() > student2.getAge()){
            return 1;
        }
        if (student1.getAge() < student2.getAge()){
            return -1;
        }
        return 0;
    }
}


public class TestTreeSet {
    public static void main(String[] args) {

        TreeSet treeSet2 = new TreeSet(new MyComparator());
        treeSet2.add(new Student("张三",19));
        treeSet2.add(new Student("李四",26));
        treeSet2.add(new Student("王五",21));
        System.out.println(treeSet2);// [Student{name='张三', age=19}, Student{name='王五', age=21}, Student{name='李四', age=26}]

    }
}

4、Map集合

1)HashMap的基本操作(HashMap时键值对的形式存储,且key唯一不可重复)

创建HashMap对象

public class TestHashMap {
    public static void main(String[] args) {
        //默认容量为16,负载因子为0.75
        Map map = new HashMap();
        HashMap map1 = new HashMap(15);
        HashMap map2 = new HashMap(10,0.78f);
    }
}

HashMap的添加操作

put(key,value):添加一个键值对,当key的值重复时,value的值会被新添加的元素覆盖

putAll(Map map):将另一个map的所有内容添加到当前map对象中

putIfAbsent(key,value):添加一个键值对,如果key的值已经在map中存在,则不添加到map中

public class TestHashMap {
    public static void main(String[] args) {
        //默认容量为16,负载因子为0.75
        Map map = new HashMap();
        HashMap map1 = new HashMap(15);
        HashMap map2 = new HashMap(10,0.78f);

        //添加操作
        map.put("name","zjw");
        map.put("age",18);
        map.put("school","zut");
        map.put("age",23);
        System.out.println(map);

        map1.put("k1","v1");
        map1.put("k2","v2");
        //putAll:添加一个map对象中的全部内容
        map.putAll(map1);
        System.out.println(map);

        //putIfAbsent:如果key存在,则不放入map中
        map.putIfAbsent("age",24);
        map.putIfAbsent("phone","183");
        System.out.println(map);
    }
}

删除操作

remove(key):移除指定key的元素

        //删除操作
        map.remove("k2");
        System.out.println(map);

修改操作

replace(key,value) 和put(key,value)都能达到修改的效果,修改指定key的value值

        //修改操作
        //replace 和 put都可以达到修改的效果,修改指定key的value值
        map.replace("k1","value");
        System.out.println(map);

查询操作

get(key):获取key的value值

size():获取map对象中元素的个数

containsKey(key):返回值为布尔类型,是否包含key为指定内容的元素

containsValue(value):返回值为布尔类型,是否包含value为指定内容的元素

keySet():返回值为Set对象,获取map中的所有key值,并存储为Set对象,使用for each 遍历keySet,输出key和value

        //keySet获取map中的所有key值,返回值为Set
        Set keySet = map.keySet();
        System.out.println(keySet);

        for (Object o : keySet ) {
            System.out.println(o+"------------->"+map.get(o));
        }

2)HashMap在内存中的存储结构如下图

使用了三种存储结构(数组,单向链表,红黑树)

  1. 创建一个初始容量为16的数组;
  2. 根据key的hash值求出在数组中的位置,key. hash%16得到0~15之间的值,即数组下标,其中存储的是添加元素(node对象)的地址;
  3. 创建一个Node对象,有key, value, next三个属性,next的值刚开始为null ;
  4. 如果经过hash运算得到的结果相同,这种情况称为hash碰撞( 冲突),然后在通过equals作比较,如果不同就使用尾插法挂在链表上,next的值为新创建的node对象的地址,这个链表结构为单向链表(添加和查询效率比较低,需要逐个查询)。
  5. 如果hash冲突的数量达到8个,将转换为红黑树的结构(优点:添加、查询效率高;缺点:创建时非常耗费资源)

jdk1.7和jdk1.8中hashMap的区别

jdk1.7中:数组+单向链表   而链表的插入为头部插入(容易造成死循环)

jdk1.8中:数组+单向链表+红黑树    链表的插入方式为尾部插入,当hash碰撞的个数超过8个时,单向链表转换为红黑二叉树。

3)hashMap集合的put方法的源码

    public V put(K key, V value) {
        //hash(key):根据key的hash值进行运算
        return putVal(hash(key), key, value, false, true);
    }

    static final int hash(Object key) {
        int h;
        //返回值为(h = key.hashCode()) ^ (h >>> 16)的值,
        //也就是hashcode和无符号右移16位的hashcode后得到的值做异或运算
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //比较两个元素的hash值是否相等,如果hash值相等在判断key值是否相等,相等则覆盖
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    //寻找单向链表的最后一个节点
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果链表长度>=8
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //转换成红黑二叉树结构
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值