Java高级 - 集合

39 阅读28分钟

Java集合框架概述

数组的特点:

  1. 长度不可变。
  2. 类型不可变。
  3. 方法和属性少,使用不便。
  4. 有序可重复。

Java 集合可分为 Collection 和 Map 两种体系

Collection接口继承树

Map接口继承树

Collection接口方法

二、集合框架
 |----Collection接口:单列集合,用来存储一个一个的对象
     |----List接口:存储有序的、可重复的数据。  -->“动态”数组
         |----ArrayList、LinkedList、Vector
     |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
         |----HashSet、LinkedHashSet、TreeSet
 |----Map接口:双列集合,用来存储一对(key - value)一对的数据   -->高中函数:y = f(x)
         |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

package com.atguigu.java;

import java.util.Objects;

/**
 * @author shkstart
 * @create 2019 上午 10:06
 */
public class Person {

    private String name;
    private int age;

    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    @Override
    public boolean equals(Object o) {
        System.out.println("Person equals()....");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {

        return Objects.hash(name, age);
    }
}

向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()。因为调用 contains方法 会调用 equals方法 去判断是否重复。

/**
 * Collection接口中声明的方法的测试
 *
 * 结论:
 * 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
 *
 * @author shkstart
 * @create 2019 上午 10:04
 */
@Test
public void test1(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
//        Person p = new Person("Jerry",20);
//        coll.add(p);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);
    //1.contains(Object obj):判断当前集合中是否包含obj
    //我们在判断时会调用obj对象所在类的equals()。
    boolean contains = coll.contains(123);
    System.out.println(contains);
    System.out.println(coll.contains(new String("Tom")));
//        System.out.println(coll.contains(p));//true
    System.out.println(coll.contains(new Person("Jerry",20)));//false -->true,重写equals()就可以比较对象了。contains判断相同默认调用equals方法。
    //结论:
    //向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().

    //2.containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中。
    Collection coll1 = Arrays.asList(123,4567);
    System.out.println(coll.containsAll(coll1));
}

注意:remove 和 removeAll 的区别

@Test
public void test2(){
    //3.remove(Object obj):从当前集合中移除obj元素。
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);

    coll.remove(1234);
    System.out.println(coll);

    coll.remove(new Person("Jerry",20));
    System.out.println(coll);

    //4. removeAll(Collection coll1):差集:从当前集合中移除coll1中所有的元素。
    Collection coll1 = Arrays.asList(123,456);
    coll.removeAll(coll1);
    System.out.println(coll);


}
@Test
public void test3(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);

    //5.retainAll(Collection coll1):交集:获取当前集合和coll1集合的交集,并返回给当前集合
//        Collection coll1 = Arrays.asList(123,456,789);
//        coll.retainAll(coll1);
//        System.out.println(coll);

    //6.equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同。
    Collection coll1 = new ArrayList();
    coll1.add(456);
    coll1.add(123);
    coll1.add(new Person("Jerry",20));
    coll1.add(new String("Tom"));
    coll1.add(false);

    System.out.println(coll.equals(coll1));


}

集合 --->数组:toArray()

数组 --->集合:调用Arrays类的静态方法asList()

/**
 * 集合元素的遍历操作,使用迭代器Iterator接口
 * 1.内部的方法:hasNext() 和  next()
 * 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,
 * 默认游标都在集合的第一个元素之前。
 * 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
 *
 * @author shkstart
 * @create 2019 上午 10:44
 */
@Test
public void test4(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);

    //7.hashCode():返回当前对象的哈希值
    System.out.println(coll.hashCode());

    //8.集合 --->数组:toArray()
    Object[] arr = coll.toArray();
    for(int i = 0;i < arr.length;i++){
        System.out.println(arr[i]);
    }

    //拓展:数组 --->集合:调用Arrays类的静态方法asList()
    List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
    System.out.println(list);

    //这一个添加会当成一个元素
    List arr1 = Arrays.asList(new int[]{123, 456});
    System.out.println(arr1.size());//1

    List arr2 = Arrays.asList(new Integer[]{123, 456});
    System.out.println(arr2.size());//2


}

Iterator迭代器接口(只针对Collection)




迭代器Iterator

/**
 * 集合元素的遍历操作,使用迭代器Iterator接口
 * 1.内部的方法:hasNext() 和  next()
 * 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,
 * 默认游标都在集合的第一个元素之前。
 * 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
 *
 * @author shkstart
 * @create 2019 上午 10:44
 */
@Test
public void test1(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);

    Iterator iterator = coll.iterator();

    //方式三:推荐
    ////hasNext():判断是否还有下一个元素
    while(iterator.hasNext()){
        //next():①指针下移 ②将下移以后集合位置上的元素返回
        System.out.println(iterator.next());
    }

}

错误写法

@Test
public void test2(){

    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);

    //错误方式一:
    // 问题①:会出现NoSuchElementException
    // 问题②:输出会跳着来,因为判断条件已经执行了一次了,输出的时候再执行一次
//        Iterator iterator = coll.iterator();
//        while((iterator.next()) != null){
//            System.out.println(iterator.next());
//        }

    //错误方式二:死循环,一直输出第一个元素。
    //集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
    while (coll.iterator().hasNext()){
        System.out.println(coll.iterator().next());
    }


}

Iterator接口remove()方法


使用 foreach 循环遍历集合元素(底层也是用迭代器)


练习:判断输出结果是什么?

public class tes {
    public static void main(String[] args) {
        String[] str = new String[5];
        for (String myStr : str) {
            myStr = "atguigu";
            System.out.println(myStr);
        }
        for (int i = 0; i < str.length; i++) {
            System.out.println(str[i]);
        }
    }
}

输出结果:

atguigu
atguigu
atguigu
atguigu
atguigu
null
null
null
null
null

解析:

输出 “atguigu” 很正常,因为 “atguigu” 是在输出前进行赋值的。但是此赋值操作只修改增强for循环迭代的当前对象,而不是数组中的原始元素。而后面输出 null 是因为 String[] str = new String[5]; 没有进行赋值操作,数组内部没有任何元素,String包装类的默认值就是 null 了。

要想修改数组的值,就得对数组一个一个索引对应的地址值所指向的元素进行修改。

for (int i = 0; i < str.length; i++) {
    str[i] = "tes";
}

Collection子接口一:List

List接口方法

public class ListTest {

    /*
    void add(int index, Object ele):在index位置插入ele元素
    boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
    Object get(int index):获取指定index位置的元素
    int indexOf(Object obj):返回obj在集合中首次出现的位置
    int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
    Object remove(int index):移除指定index位置的元素,并返回此元素
    Object set(int index, Object ele):设置指定index位置的元素为ele
    List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
    
    总结:常用方法
    增:add(Object obj)
    删:remove(int index) / remove(Object obj)
    改:set(int index, Object ele)
    查:get(int index)
    插:add(int index, Object ele)
    长度:size()
    遍历:① Iterator迭代器方式
         ② 增强for循环
         ③ 普通的循环

     */
    @Test
    public void test3(){
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add("AA");

        //方式一:Iterator迭代器方式
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println("***************");

        //方式二:增强for循环
        for(Object obj : list){
            System.out.println(obj);
        }

        System.out.println("***************");

        //方式三:普通for循环
        for(int i = 0;i < list.size();i++){
            System.out.println(list.get(i));
        }



    }


    @Test
    public void test2(){
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add("AA");
        list.add(new Person("Tom",12));
        list.add(456);
        //int indexOf(Object obj):返回obj在集合中首次出现的位置。如果不存在,返回-1.
        int index = list.indexOf(4567);
        System.out.println(index);

        //int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置。如果不存在,返回-1.
        System.out.println(list.lastIndexOf(456));

        //Object remove(int index):移除指定index位置的元素,并返回此元素
        Object obj = list.remove(0);
        System.out.println(obj);
        System.out.println(list);

        //Object set(int index, Object ele):设置指定index位置的元素为ele
        list.set(1,"CC");
        System.out.println(list);

        //List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开区间的子集合
        List subList = list.subList(2, 4);
        System.out.println(subList);
        System.out.println(list);


    }


    @Test
    public void test1(){
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add("AA");
        list.add(new Person("Tom",12));
        list.add(456);

        System.out.println(list);

        //void add(int index, Object ele):在index位置插入ele元素
        list.add(1,"BB");
        System.out.println(list);

        //boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
        List list1 = Arrays.asList(1, 2, 3);
        list.addAll(list1);
//        list.add(list1);
        System.out.println(list.size());//9

        //Object get(int index):获取指定index位置的元素
        System.out.println(list.get(0));

    }


}

List实现类之一:ArrayList

笔试题

解析:

这样写是默认把2当成索引,而不是对象,虽然remove有两个方法:

删:remove(int index) / remove(Object obj)

但是,参数默认最简化,2就当成索引,而不是装箱再当成对象2。要想删掉元素2,传参改成对象2就行了。

public class ListExer {
    /*
    区分List中remove(int index)和remove(Object obj)
     */
    @Test
    public void testListRemove() {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        updateList(list);
        System.out.println(list);//
    }

    private void updateList(List list) {
//        list.remove(2);
        list.remove(new Integer(2));
    }

}


ArraysList源码

源码逐段解析

底层数据结构

//底层数据结构是数组
/**
 * The array buffer into which the elements of the ArrayList are stored.	数组缓冲区,数组列表的元素被存储在其中。
 * The capacity of the ArrayList is the length of this array buffer.		数组列表的容量就是这个数组缓冲区的长度。
 */
private transient Object[] elementData;

构造器

JDK7

/**
 * Constructs an empty list with an initial capacity of ten.	构造一个初始容量为10的空列表。
 */
public ArrayList() {
    this(10);
}

JDK8

一开始先不去创建,用到的时候才去创建数组,省下来了内存空间

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
	用于默认大小的空实例的共享空数组实例。我们将它与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时要膨胀多少。

 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

创建数组

/**
 * Constructs an empty list with the specified initial capacity.
	构造具有指定初始容量的空列表。
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity is negative		如果指定的初始容量为负数
 */
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
}

添加操作

JDK7

/**
 * Appends the specified element to the end of this list.
	将指定元素追加到此列表的末尾
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

判断是否超出数组容量,ensureCapacityInternal

JDK7

//判断是否超出数组容量				当前size + 1(要设置的容量)
private void ensureCapacityInternal(int minCapacity) {
    modCount++;
    // overflow-conscious code
// 当前size + 1(要设置的容量),elementData.length默认的初始值是10
    if (minCapacity - elementData.length > 0)
        // 扩容
        grow(minCapacity);
}

JDK8

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

扩容

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.	
增加容量,以确保它至少可以容纳由 minCapacity 参数指定的元素数量。
 *
 * @param minCapacity the desired minimum capacity
 */
                // 当前size + 1(要设置的容量)
private void grow(int minCapacity) {
    // overflow-conscious code
    // 老容量
    int oldCapacity = elementData.length;
	// 新容量 = 1.5倍老容量(Stringbuffer 扩容为原来两倍加个二)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
	// 扩容后还是小,那就把当前想要设置的值设置为容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
	// 超过整型的最大值,那就取最大值Integer.MAX_VALUE - 8
    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. ArrayList的源码分析:
2.1 jdk 7情况下
   ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
   list.add(123);//elementData[0] = new Integer(123);
   ...
   list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
   默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

   结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

2.2 jdk 8中ArrayList的变化:
   ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组

   list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
   ...
   后续的添加和扩容操作与jdk 7 无异。
2.3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

List实现类之二:LinkedList

重要:要求会画图,会写数据结构。

List 实现类之三:Vector

List常见面试题

1、请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?
	ArrayList和LinkedList的异同
	①二者都线程不安全,相对线程安全的Vector,执行效率高。
	②此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于
随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增
和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。

	ArrayList和Vector的区别
	①Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用
ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。
	②Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

Collection子接口二:Set

 一、Set:存储无序的、不可重复的数据
    以HashSet为例说明:
    1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。

    2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。

无序性主要是从 数据结构、在内存的分布 来讲的,数组结构是一块连续内存空间,所以叫有序性,Set不是一块连续的内存空间,所以无序性。

Set实现类之一:HashSet

HashSet底层原理分析

注意:

看最后一点,所以要设置好hashcode方法,不然hashset可能会存在重复元素。

只要equals()或者hashCode()方法其中有一个没有重写好,就相当于两个对象会判断成不一样,所以集合中会出现相同的两个元素。

两个方法的作用:

hashCode()作用:①决定位置。②决定第一次对象的判断。

equals()作用:进行第二次判断


hash值的作用:

①用来代入散列算法算存放位置的。

②用来比较对应位置上面元素的hash值。

二、添加元素的过程:以HashSet为例:
    我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
    此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
    数组此位置上是否已经有元素:
        如果此位置上没有其他元素,则元素a添加成功。 --->情况1
        如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
            如果hash值不相同,则元素a添加成功。--->情况2
            如果hash值相同,进而需要调用元素a所在类的equals()方法:
                   equals()返回true,元素a添加失败
                   equals()返回false,则元素a添加成功。--->情况2

    对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
    jdk 7 :元素a放到数组中,指向原来的元素。
    jdk 8 :原来的元素在数组中,指向元素a
    总结:七上八下
    
    HashSet底层:数组+链表的结构。

重写 hashCode()

重写 equals()

了解即可

HashSet常用的方法(和Collection一样,因为没有新增方法)

package com.atguigu.java1;

import org.junit.Test;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * 1. Set接口的框架:
 *
 * |----Collection接口:单列集合,用来存储一个一个的对象
 *          |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
 *              |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
 *                  |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
 *                                      对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
 *              |----TreeSet:可以按照添加对象的指定属性,进行排序。
 *
 *
 *  1. Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
 *
 *  2. 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
 *     要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
 *      重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
 *
 *
 * @author shkstart
 * @create 2019 下午 3:40
 */
public class SetTest {
    /*
    一、Set:存储无序的、不可重复的数据
    以HashSet为例说明:
    1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。

    2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。

    二、添加元素的过程:以HashSet为例:
        我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
        此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
        数组此位置上是否已经有元素:
            如果此位置上没有其他元素,则元素a添加成功。 --->情况1
            如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
                如果hash值不相同,则元素a添加成功。--->情况2
                如果hash值相同,进而需要调用元素a所在类的equals()方法:
                       equals()返回true,元素a添加失败
                       equals()返回false,则元素a添加成功。--->情况2

        对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
        jdk 7 :元素a放到数组中,指向原来的元素。
        jdk 8 :原来的元素在数组中,指向元素a
        总结:七上八下

        HashSet底层:数组+链表的结构。

     */

    @Test
    public void test1(){
        Set set = new HashSet();
        set.add(456);
        set.add(123);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom",12));
        set.add(new User("Tom",12));
        set.add(129);

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

    }
}

Set实现类之二:LinkedHashSet

LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,
    对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
    因为维护了一个双向链表,直接顺着双向链表去遍历就行了。

Set实现类之三:TreeSet

package com.atguigu.java1;

import org.junit.Test;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

/**
 * @author shkstart
 * @create 2019 下午 4:59
 */
public class TreeSetTest {

    /*
    1.向TreeSet中添加的数据,要求是相同类的对象。
    2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)


    3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
    4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
     */
    @Test
    public void test1(){
        TreeSet set = new TreeSet();

        //失败:不能添加不同类的对象
//        set.add(123);
//        set.add(456);
//        set.add("AA");
//        set.add(new User("Tom",12));

            //举例一:
//        set.add(34);
//        set.add(-34);
//        set.add(43);
//        set.add(11);
//        set.add(8);

        //举例二:
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));


        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }

    @Test
    public void test2(){
        Comparator com = new Comparator() {
            //按照年龄从小到大排列
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else{
                    throw new RuntimeException("输入的数据类型不匹配");
                }
            }
        };

        TreeSet set = new TreeSet(com);
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Mary",33));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));


        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

}

集合练习

1.集合Collection中存储的如果是自定义类的对象,需要自定义类重写哪个方法?为什么?
equals()方法。  contains() /remove()/retainsAll() ….等操作会用到equals()方法。
List:equals()方法
Set:(HashSet、LinkedHashSet为例):equals()、hashCode()
     (TreeSet为例):Comparable:compareTo(Object obj)
						 Comparator:compare(Object o1,Object o2)

    
2.ArrayList,LinkedList,Vector三者的相同点与不同点?【面试题】


3.List 接口的常用方法有哪些?(增、删、改、查、插、长度、遍历)
add(Object obj)
remove(Object obj)/remove(int index)
set(int index,Object obj)
get(int index)
add(int index,Object obj)
size() 		返回的是元素的个数,而不是数组的长度
使用Iterator;foreach;普通的for

4.如何使用Iterator和增强for循环遍历List。举例说明

    
5.Set存储数据的特点是什么?常见的实现类有什么?说明一下彼此的特点。
HashSet  LinkedHashSet  TreeSet		
这几个set的底层就是下面的map,新建一个set就是新建一个map
    
HashMap    LinkedHashMap   TreeMap

面试题

第一题

自定义的类去重的话,要重写hashcode和equals方法

//练习:在List内去除重复数字值,要求尽量简单
public static List duplicateList(List list) {
    HashSet set = new HashSet();
    set.addAll(list);
    return new ArrayList(set);
}
@Test
public void test2(){
    List list = new ArrayList();
    list.add(new Integer(1));
    list.add(new Integer(2));
    list.add(new Integer(2));
    list.add(new Integer(4));
    list.add(new Integer(4));
    List list2 = duplicateList(list);
    for (Object integer : list2) {
        System.out.println(integer);
    }
}

第二题(经典题)

第一次写的答案

正确答案

分析

这一题主要考察的是对hashset的元素添加的过程,还有对java值传递的理解。与list不同的是set会重写hashcode方法,而list直接用equals去比较元素。

第一个remove猜错了的话,后面的会全错,第一个remove是删除不了的,因为修改了p1之后hashcode的值已经变了,所以在改动后的p1所对应的hashcode位置,找不到原来的元素,所以删除不了。(hashcode不一样,equals一样)

第二个添加操作是添加成功的,因为位置上没有元素。(hashcode不一样,equals一样)

第三个也是添加成功的,因为AA那个p1位置上的元素已经被修改了改成了cc了,所以equals方法会不相等,所以可以添加成功。(hashcode一样,equals不一样)

源代码

@Test
public void test3(){
    HashSet set = new HashSet();
    Person p1 = new Person(1001,"AA");
    Person p2 = new Person(1002,"BB");

    set.add(p1);
    set.add(p2);
    System.out.println(set);

    p1.name = "CC";
    set.remove(p1);
    System.out.println(set);
    set.add(new Person(1001,"CC"));
    System.out.println(set);
    set.add(new Person(1001,"AA"));
    System.out.println(set);

}

(重点)Map接口(大多数都是八股文,后面背一下)

map的底层结构

二、Map结构的理解:

  • Map中的key:无序的、不可重复的,使用Set存储所有的key  ---> key所在的类要重写equals()和hashCode() (以HashMap为例) (重写hashCode() 都是为了提高查找效率)
  • Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
  • 一个键值对:key-value构成了一个Entry对象。
  • Map中的entry:无序的、不可重复的,使用Set存储所有的entry

Map实现类之一:HashMap

Map接口:常用方法

package com.atguigu.java;

import org.junit.Test;

import java.util.*;

/**
 * 一、Map的实现类的结构:
 *  |----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
 *         |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
 *              |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
 *                      原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
 *                      对于频繁的遍历操作,此类执行效率高于HashMap。
 *         |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
 *                      底层使用红黑树
 *         |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
 *              |----Properties:常用来处理配置文件。key和value都是String类型
 *
 *
 *      HashMap的底层:数组+链表  (jdk7及之前)
 *                    数组+链表+红黑树 (jdk 8)
 *
 *
 *  面试题:
 *  1. HashMap的底层实现原理?
 *  2. HashMap 和 Hashtable的异同?
 *  3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
 *
 *  二、Map结构的理解:
 *    Map中的key:无序的、不可重复的,使用Set存储所有的key  ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
 *    Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
 *    一个键值对:key-value构成了一个Entry对象。
 *    Map中的entry:无序的、不可重复的,使用Set存储所有的entry
 *
 *  三、HashMap的底层实现原理?以jdk7为例说明:
 *      HashMap map = new HashMap():
 *      在实例化以后,底层创建了长度是16的一维数组Entry[] table。
 *      ...可能已经执行过多次put...
 *      map.put(key1,value1):
 *      首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
 *      如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
 *      如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据
 *      的哈希值:
 *              如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
 *              如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
 *                      如果equals()返回false:此时key1-value1添加成功。----情况3
 *                      如果equals()返回true:使用value1替换value2。
 *
 *       补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
 *
 *      在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
 *
 *      jdk8 相较于jdk7在底层实现方面的不同:
 *      1. new HashMap():底层没有创建一个长度为16的数组
 *      2. jdk 8底层的数组是:Node[],而非Entry[]
 *      3. 首次调用put()方法时,底层创建长度为16的数组
 *      4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
 *         4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
           4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
 *
 *      DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
 *      DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
 *      threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
 *      TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
 *      MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
 *
 *  四、LinkedHashMap的底层实现原理(了解)
 *      源码中:
 *      static class Entry<K,V> extends HashMap.Node<K,V> {
             Entry<K,V> before, after;//能够记录添加的元素的先后顺序
             Entry(int hash, K key, V value, Node<K,V> next) {
                super(hash, key, value, next);
             }
         }
 *
 *
 *   五、Map中定义的方法:
 添加、删除、修改操作:
 Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
 void putAll(Map m):将m中的所有key-value对存放到当前map中
 Object remove(Object key):移除指定key的key-value对,并返回value
 void clear():清空当前map中的所有数据
 元素查询的操作:
 Object get(Object key):获取指定key对应的value
 boolean containsKey(Object key):是否包含指定的key
 boolean containsValue(Object value):是否包含指定的value
 int size():返回map中key-value对的个数
 boolean isEmpty():判断当前map是否为空
 boolean equals(Object obj):判断当前map和参数对象obj是否相等
 元视图操作的方法:
 Set keySet():返回所有key构成的Set集合
 Collection values():返回所有value构成的Collection集合
 Set entrySet():返回所有key-value对构成的Set集合

 *总结:常用方法:
 * 添加:put(Object key,Object value)
 * 删除:remove(Object key)
 * 修改:put(Object key,Object value)
 * 查询:get(Object key)
 * 长度:size()
 * 遍历:keySet() / values() / entrySet()
 *
 *
 * @author shkstart
 * @create 2019 上午 11:15
 */
public class MapTest {

    /*
 元视图操作的方法:
 Set keySet():返回所有key构成的Set集合
 Collection values():返回所有value构成的Collection集合
 Set entrySet():返回所有key-value对构成的Set集合

     */


    @Test
    public void test5(){
        Map map = new HashMap();
        map.put("AA",123);
        map.put(45,1234);
        map.put("BB",56);

        //遍历所有的key集:keySet()
        Set set = map.keySet();
            Iterator iterator = set.iterator();
            while(iterator.hasNext()){
                System.out.println(iterator.next());
        }
        System.out.println();
        //遍历所有的value集:values()
        Collection values = map.values();
        for(Object obj : values){
            System.out.println(obj);
        }
        System.out.println();
        //遍历所有的key-value
        //方式一:entrySet()
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            Object obj = iterator1.next();
            //entrySet集合中的元素都是entry
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + "---->" + entry.getValue());

        }
        System.out.println();
        //方式二:
        Set keySet = map.keySet();
        Iterator iterator2 = keySet.iterator();
        while(iterator2.hasNext()){
            Object key = iterator2.next();
            Object value = map.get(key);
            System.out.println(key + "=====" + value);

        }

    }


    /*
 元素查询的操作:
 Object get(Object key):获取指定key对应的value
 boolean containsKey(Object key):是否包含指定的key
 boolean containsValue(Object value):是否包含指定的value
 int size():返回map中key-value对的个数
 boolean isEmpty():判断当前map是否为空
 boolean equals(Object obj):判断当前map和参数对象obj是否相等
     */
    @Test
    public void test4(){
        Map map = new HashMap();
        map.put("AA",123);
        map.put(45,123);
        map.put("BB",56);
        // Object get(Object key)
        System.out.println(map.get(45));
        //containsKey(Object key)
        boolean isExist = map.containsKey("BB");
        System.out.println(isExist);

        isExist = map.containsValue(123);
        System.out.println(isExist);

        map.clear();

        System.out.println(map.isEmpty());

    }

    /*
     添加、删除、修改操作:
 Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
 void putAll(Map m):将m中的所有key-value对存放到当前map中
 Object remove(Object key):移除指定key的key-value对,并返回value
 void clear():清空当前map中的所有数据
     */
    @Test
    public void test3(){
        Map map = new HashMap();
        //添加
        map.put("AA",123);
        map.put(45,123);
        map.put("BB",56);
        //修改
        map.put("AA",87);

        System.out.println(map);

        Map map1 = new HashMap();
        map1.put("CC",123);
        map1.put("DD",123);

        map.putAll(map1);

        System.out.println(map);

        //remove(Object key)
        Object value = map.remove("CC");
        System.out.println(value);
        System.out.println(map);

        //clear()
        map.clear();//与map = null操作不同
        System.out.println(map.size());
        System.out.println(map);
    }

    @Test
    public void test2(){
        Map map = new HashMap();
        map = new LinkedHashMap();
        map.put(123,"AA");
        map.put(345,"BB");
        map.put(12,"CC");

        System.out.println(map);
    }


    @Test
    public void test1(){
        Map map = new HashMap();
//        map = new Hashtable();
        map.put(null,123);

    }
}

(重点)HashMap的底层实现原理(添加和扩容步骤)

JDK8之前

JDK8

还没细看

源码讲解视频:www.bilibili.com/video/BV1Kb…

HashMap的底层实现原理?以jdk7为例说明 (参照这上面的 JDK 7 的图来看):

HashMap map = new HashMap():

在实例化以后,底层创建了长度是16的一维数组Entry[] table。
...可能已经执行过多次put...
map.put(key1,value1):

首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算(hash一下,例如,123456 % 16,123456是计算出来的值,16是数组的长度)以后,得到在Entry数组中的存放位置。
  如果此位置上的数据为空,此时的key1-value1(一个entry)添加成功。 ----情况1
  如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
          如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
          如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
                  如果equals()返回false:此时key1-value1添加成功。----情况3
                  如果equals()返回true:使用value1替换value2。

 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。


**************************上面都是jdk7的**************************



jdk8 相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没有创建一个长度为16的数组
2. jdk 8底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层创建长度为16的数组
4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
   4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
   4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。


DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64


四、LinkedHashMap的底层实现原理(了解)
  源码中:
  static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;//能够记录添加的元素的先后顺序
    Entry(int hash, K key, V value, Node<K,V> next) {
       super(hash, key, value, next);
    }
}
HashMap扩容后是否需要rehash?

需要的

链接:www.cnblogs.com/zwh0910/p/1…

面试题

Map实现类之二:LinkedHashMap

node的实现有一点区别,linkedhashmap多维护了一个前和后的属性。

Map实现类之三:TreeMap

package com.atguigu.java;

import org.junit.Test;

import java.util.*;

/**
 * @author shkstart
 * @create 2019 下午 3:46
 */
public class TreeMapTest {

    //向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
    //因为要按照key进行排序:自然排序 、定制排序
    //自然排序
    @Test
    public void test1(){
        TreeMap map = new TreeMap();
        User u1 = new User("Tom",23);
        User u2 = new User("Jerry",32);
        User u3 = new User("Jack",20);
        User u4 = new User("Rose",18);

        map.put(u1,98);
        map.put(u2,89);
        map.put(u3,76);
        map.put(u4,100);

        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + "---->" + entry.getValue());

        }
    }

    //定制排序
    @Test
    public void test2(){
        TreeMap map = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }
                throw new RuntimeException("输入的类型不匹配!");
            }
        });
        User u1 = new User("Tom",23);
        User u2 = new User("Jerry",32);
        User u3 = new User("Jack",20);
        User u4 = new User("Rose",18);

        map.put(u1,98);
        map.put(u2,89);
        map.put(u3,76);
        map.put(u4,100);

        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            Object obj = iterator1.next();
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + "---->" + entry.getValue());

        }
    }


}
package com.atguigu.java;

/**
 * @author shkstart
 * @create 2019 下午 3:56
 */
public class User implements Comparable{
    private String name;
    private int age;

    public User() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    @Override
    public boolean equals(Object o) {
        System.out.println("User equals()....");
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (age != user.age) return false;
        return name != null ? name.equals(user.name) : user.name == null;
    }

    @Override
    public int hashCode() { //return name.hashCode() + age;
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    //按照姓名从大到小排列,年龄从小到大排列
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
//            return -this.name.compareTo(user.name);
            int compare = -this.name.compareTo(user.name);
            if(compare != 0){
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入的类型不匹配");
        }

    }
}

Map实现类之四:Hashtable

Map实现类之五:Properties

Collections工具类

package com.atguigu.java;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Collections:操作Collection、Map的工具类
 *
 *
 * 面试题:Collection 和 Collections的区别?
 *
 *
 * @author shkstart
 * @create 2019 下午 4:19
 */
public class CollectionsTest {

/*
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值

 */
    @Test
    public void test2(){
        List list = new ArrayList();
        list.add(123);
        list.add(43);
        list.add(765);
        list.add(-97);
        list.add(0);

        //报异常:IndexOutOfBoundsException("Source does not fit in dest")
//        List dest = new ArrayList();
//        Collections.copy(dest,list);
        //正确的:
        List dest = Arrays.asList(new Object[list.size()]);
        System.out.println(dest.size());//list.size();
        Collections.copy(dest,list);

        System.out.println(dest);


        /*
        Collections 类中提供了多个 synchronizedXxx() 方法,
        该方法可使将指定集合包装成线程同步的集合,从而可以解决
        多线程并发访问集合时的线程安全问题

         */
        //返回的list1即为线程安全的List
        List list1 = Collections.synchronizedList(list);


    }

    @Test
    public void test1(){
        List list = new ArrayList();
        list.add(123);
        list.add(43);
        list.add(765);
        list.add(765);
        list.add(765);
        list.add(-97);
        list.add(0);

        System.out.println(list);

//        Collections.reverse(list);
        //打乱数组元素
//        Collections.shuffle(list);
//        Collections.sort(list);
//        Collections.swap(list,1,2);
        //返回一个元素出现次数
        int frequency = Collections.frequency(list, 123);

        System.out.println(list);
        System.out.println(frequency);

    }

}

练习

1.Map存储数据的特点是什么?并指明key,value,entry存储数据的特点。
双列数据,存储key-value对数据。
key:无序的、不可重复的-Set存储
value:无序的、可重复的 –>Collection存储
key-value:无序的、不可重复 Set存储
    
2.描述HashMap的底层实现原理(jdk 8版)
    

3. Map中常用实现类有哪些?各自有什么特点?
    
    

4. 如何遍历Map中的key-value对,代码实现
    
    

5. Collection和Collections的区别?

    

小结

1、增强for循环 底层是用迭代器 Iterator 的。

2、ArrayList的底层源码和 Vector的源码大同小异,只是Vector里面的方法前面有个synchronize修饰,而且扩容机制是扩容2倍。

3、 集合 --->数组:toArray()

数组 --->集合:调用Arrays类的静态方法asList()

4、javajgs.com/archives/19…