第十一章 集合

目录

一、集合框架概述

1.理解集合

2.集合使用场景

二、Collection接口的常用方法

1.常用方法1                

 2.常用方法2

3.常用方法3

三、Iterator迭代器

1.使用Iterator遍历Collection

2.Iterator迭代器的执行原理

3.Iterator迭代器的两种错误写法

4.Iterator迭代器remove()的使用

四、新特性foreach循环遍历集合

五、List接口

1.List接口常用实现类的对比

2.ArrayList的源码分析

3.LinkedList的源码分析

4.Vector的源码分析

5.ArrayList、LinkedList、Vector三者的异同

6.List接口中的常用方法

7.List遍历及方法总结

六、Set接口

1.Set接口实现类的对比

2.Set的无序性和不可重复性的理解

3.HashSet中元素的添加过程

4.关于hashCode()和equals()的重写

5.LinkedHashSet的使用

6.TreeSet的自然排序和定制排序

七、Map接口

1.Map接口及其多个实现类的对比

2.对Map中key、value的理解

3.HashMap在jdk7中的底层实现原理

4.HashMap在jdk8中的底层实现原理

5.HashMap在jdk7中的源码分析

6.HashMap在jdk8中的源码分析

7.LinkedHashMap的底层实现

8.Map中的常用方法1

9.Map中的常用方法2

10.TreeMap的两种添加方式

11.Properties处理属性文件

12.Collections工具类常用方法


Collection接口和Map接口是并列关系,
存储数据的特点不同。
学习要求:学完后要知道什么类型的数据适合什么结构去存储,选择合适的集合类去实现数据的保存,调用相关方法。不同集合类底层数据结构为何?如何实现数据的操作的:增删改查等

一、集合框架概述

1.理解集合

* 1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
*  说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)
*  //【持久化的存储:硬盘、服务器、数据库。讲到IO流时再讲】
*
* 2.1 数组在存储多个数据方面的特点:
*      > 一旦初始化以后,其长度就确定了。
*      > 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
*      //【严格是好处,放了不同类型就报错.
*      //集合类型不确定,什么都能往里放,并不好,不严格
*      //泛型能解决这件事】
*       比如:String[] arr;int[] arr1;Object[] arr2;
*       //【Object[] arr2;可以多态】
*
* 2.2 数组在存储多个数据方面的缺点:
*      > 一旦初始化以后,其长度就不可修改。
*      > 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
*      //【添加、删除、插入数据都要挪元素,没有方法可以直接拿来调用。数组只是数据结构中的一种,集合设计的数据结构很丰富:链表二叉树等,添加、删除、插入效率更高】
*
*      > 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
*      > 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
* 二、集合框架
*      |----Collection接口:单列集合,用来存储一个一个的对象
*          |----List接口:存储有序的、可重复的数据。  -->“动态”数组
*          //【和数组特点很像】
*              |----ArrayList、LinkedList、Vector
*              //【ArrayList、LinkedList、Vector的不同,底层扩容什么样的?】
*
*          |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
*              |----HashSet、LinkedHashSet、TreeSet
*          //【通常用Set做一些数据过滤的问题。有一些重复数据,放入Set里,放入顺序无所谓,发现放入的数据和前面的一样,就不要了,这样就过滤掉了】
*
*      |----Map接口:双列集合,用来存储一对(key - value)一对的数据   -->高中函数:y = f(x)//【x是key,y是value】
*              |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
*              //【HashMap的底层实现原理。jdk7和8不一样

集合:Collection接口:
                >List接口(动态数组):ArrayList、LinkedList、Vector
                >Set接口(集合):HashSet、LinkedHashSet、TreeSet

          Map接口(函数):

                >HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

开发中,集合中List、Map用的最多,Set很少用
*  List用来替换数组

  理解Map接口:

2.集合使用场景

二、Collection接口的常用方法

一共15个方法

1.常用方法1                

public class CollectionTest {

    @Test
    public void test1(){
        Collection coll = new ArrayList();

        //add(Object e):将元素e添加到集合coll中
        coll.add("AA");
        coll.add("BB");
        coll.add(123);//自动装箱
        coll.add(new Date());

        //size():获取添加的元素的个数
        System.out.println(coll.size());//4

        //addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
        Collection coll1 = new ArrayList();
        coll1.add(456);
        coll1.add("CC");
        coll.addAll(coll1);

        System.out.println(coll.size());//6
        System.out.println(coll);//【coll.toString()。ArrayList里重写的】

        //clear():清空集合元素
        //【不是把coll指向null,而是让元素都为null
        // elementData[i] = null;】
        //【coll.isEmpty()也不会报空指针异常】
        coll.clear();

        //isEmpty():判断当前集合是否为空
        //【不是集合是否为null,而是判断集合中是否有元素,就是看size】
        System.out.println(coll.isEmpty());

    }

}

(1)add(Object e)

(2)addAll(Collection coll)

(3)size()

(4)clear() :

不是把coll指向null,而是让集合元素都为null
elementData[i] = null;
这样coll.isEmpty()也不会报空指针异常

(5)isEmpty():

不是集合是否为null,而是判断集合中是否有元素,就是看size是否为0

 2.常用方法2

 /* Collection接口中声明的方法的测试
 *
 * 结论:
 * 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
 */
public class CollectionTest {


    @Test
    public void test1(){
        Collection coll = new ArrayList();//【ArrayList,按顺序一个一个比较】
        coll.add(123);//【123是Integer包装类,不是int基本数据类型】
        coll.add(456);
/*
//        Person p = new Person("Jerry",20);
//        coll.add(p);
//        System.out.println(coll.contains(p));//true
          //【这样写,不管怎样都是true,
          //用Object类的equals,contains内部调==比较对象的地址值,是true,p是引用,保存了地                
            址;
          //重写了equals,调equals比较对象的内容也是true】
*/
        coll.add(new Person("Jerry",20));
        coll.add(new String("Tom"));
        coll.add(false);//【false是Boolean包装类,不是boolean基本数据类型】

        //1.contains(Object obj):判断当前集合中是否包含obj
        //我们在判断时会调用obj对象所在类的equals()。
        //【向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().】
        boolean contains = coll.contains(123);
        System.out.println(contains);
        System.out.println(coll.contains(new String("Tom")));
        //【结果是true,说明比较的是内容,用重写后的equals判断的,
        // 不是用==判断的】

        System.out.println(coll.contains(new Person("Jerry",20)));//false -->true
        //【要重写Person的equals方法,否则用的是Object类的equals方法(==比地址值)】

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

    @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);
        //【移除1234,先判断coll集合中有没有1234,所以还是要调equals】
        System.out.println(coll);

        coll.remove(new Person("Jerry",20));
        //【重写了Person类的equals,所以可以删除成功】
        System.out.println(coll);

        //4. removeAll(Collection coll1):差集:从当前集合中移除coll1中所有的元素。
        Collection coll1 = Arrays.asList(123,456);//【Arrays.asList(...)返回一个List集合】
        coll.removeAll(coll1);
        //【调了Person的equals方法】
        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);
        //【remove和retainAll都会修改coll】


        //6.equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同。
        //【调equals方法的是集合coll,形参是一个Object对象,怎样返回true?
        // Object obj也必须是集合,集合元素都和coll集合元素相同->true】
        Collection coll1 = new ArrayList();
        coll1.add(123);
        coll1.add(456);
        coll1.add(new Person("Jerry",20));
        coll1.add(new String("Tom"));
        coll1.add(false);

        /*交换位置
        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));
        //【false,因为List是有序的,内容一样顺序不一样也不相同】
        */
        System.out.println(coll.equals(coll1));//true
        //【调用了Person的equals方法。因为输出了"Person equals()...."】


    }

    @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():返回当前对象的哈希值
        //【public native int hashCode();
        // hashCode()定义在Object类中,所有对象都可以调用】
        System.out.println(coll.hashCode());

        //8.集合 --->数组:toArray()
        //【    public Object[] toArray() {
        //        return Arrays.copyOf(elementData, size);
        //    }
        // 返回值类型是Object[],
        // 前面添加的123、456、new Person、new String、false都是Object类型的元素,所以返回值也是Object[],
        // Object[]数组,表示数组元素都是Object类型的】
        Object[] arr = coll.toArray();
        for(int i = 0;i < arr.length;i++){
            System.out.println(arr[i]);
        }

        //拓展:数组 --->集合:调用Arrays类的静态方法asList()
        //【Arrays.asList(T...a),可变形参相当于数组】
        List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
        System.out.println(list);//【[AA, BB, CC]】

        List<int[]> arr3 = Arrays.asList(new int[]{123, 456});
        System.out.println(arr3);//【[[I@22927a81]】
        //【[[I@22927a81],
        // [I@22927a81一个元素,这个元素是一个一维的数组,这个数组的元素是int类型,
        // 把new int[]{123, 456}数组整体当成一个元素,不是把数组内部的元素当成几个元素。
        // 因为数组是引用数据类型,满足可变形参T...a(其实就是Object类),
        //不会把123,456自动装箱。因为一整个int[]就可以看作是一个对象
        // 所以整个数组被当作“数组这一个对象”,
        // 元素是Integer类型,包装类,就可以单独当成一个对象。】

        List arr4 = Arrays.asList(123, 456);//【删去泛型此时认为是两个元素】
        System.out.println(arr4);//[123, 456]
        System.out.println(arr4.size());//2

        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】

        //9.iterator():返回Iterator接口的实例,用于【遍历】集合元素。放在IteratorTest.java中测试

    }
}


//Person类
import java.util.Objects;

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()....");//【测试语句,看contains()是否调用了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);
    }
}

常用方法二都会调用equals,所以要求元素所在类必须重写equals()

(6)contains(Object obj):

判断当前集合中是否包含obj
在判断时会调用obj对象所在类的equals()。
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals(),否则调用的是Object类的equals,比较地址值

(7)containsAll(Collection coll)

调用了元素所属类的equals()

(8)remove(Object obj)

调用了元素所属类的equals()

(9)removeAll(Collection coll)

调了元素所属类equals()

(10)retainAll(Collection coll)

调了元素所属类equals()

(11)equals(Object obj)

调了元素所属类的equals()

3.常用方法3

(12)hashCode() :定义在 Object类中

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

Object[] arr = coll.toArray();

(14)asList() :数组-->集合

List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});

(15)iterator():

返回Iterator接口的实例,用于遍历集合元素

Collection要重写哪个方法?
* equals()
* List:equals()
* Set:(HashSet、LinkedHashSet)equals()、hashCode()
*      (TreeSet)Comparable:compareTo()
*                Comparator:compare()
*                【TreeSet调contains、remove还是要重写compare或compareTo方法而不是equals】

三、Iterator迭代器

1.使用Iterator遍历Collection

* 集合元素的遍历操作,使用迭代器Iterator接口
* //【只用来遍历Collection接口,Map接口不用迭代器遍历
* 1.内部的方法:hasNext() 和  next()
* 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,
* 默认游标都在集合的第一个元素之前。
* 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
* //【这是迭代器里的remove(),不是Collection集合中的remove()】
public class IteratorTest {

    @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();//【Iterator是接口,叫迭代器接口】
        //方式一:
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());//报异常:NoSuchElementException【没有这个元素】

        //方式二:不推荐
//        for(int i = 0;i < coll.size();i++){
//            System.out.println(iterator.next());
//        }

        //方式三:推荐
        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);

        //错误方式一:
//        Iterator iterator = coll.iterator();
//        while((iterator.next()) != null){
//            System.out.println(iterator.next());
//            //【输出:
//            // 456
//            // Tom】
//        }

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


    }

    //测试Iterator中的remove()
    //如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,
    // 再调用remove都会报IllegalStateException。
    @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);

        //删除集合中"Tom"
        Iterator iterator = coll.iterator();
        while (iterator.hasNext()){
//            iterator.remove();//【异常】
            Object obj = iterator.next();//【add时都是认为添加的是Object类型,取的时候也是Object类型】
            if("Tom".equals(obj)){//【"Tom".equals(obj)比obj.equals("Tom")健壮性更好,以防obj是null的情况】
                iterator.remove();
//                iterator.remove();
            }

        }
        //遍历集合
        iterator = coll.iterator();
        //【再次遍历要重新获取iterator指针,因为删除时用的迭代器已经指向集合中最后一个元素了】
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

2.Iterator迭代器的执行原理

3.Iterator迭代器的两种错误写法

见1

4.Iterator迭代器remove()的使用

见1

四、新特性foreach循环遍历集合

jdk 5.0 新增了foreach循环,用于遍历Collection集合、数组
for(集合元素的类型 局部变量 : 集合对象)遍历集合
for(数组元素的类型 局部变量 : 数组对象)遍历数组
【foreach内部仍然调用了迭代器。】
【Object这个位置,由coll集合中的元素是什么类型决定】
【Object obj就像for循环里的int i一样】
for(Object obj : coll){
    System.out.println(obj);
}

/*【foreach的工作过程:
coll取第一个元素赋给Object obj,然后sout打印obj
coll取第二个元素赋给Object obj,然后sout打印obj
...
coll取最后一个元素赋给Object obj,然后sout打印obj

怎么取的?
debug看一下
内部仍然调用了迭代器】
 */

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;

/**
 * jdk 5.0 新增了foreach循环,用于遍历Collection集合、数组
 *
 * @author shkstart
 * @create 2019 上午 11:24
 */
public class ForTest {

    @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);

        //for(集合元素的类型 局部变量 : 集合对象)遍历集合
        //for(数组元素的类型 局部变量 : 数组对象)遍历数组
        //【内部仍然调用了迭代器。】
        //【Object这个位置,由coll集合中的元素是什么类型决定】
        //【Object obj就像for循环里的int i一样】
        for(Object obj : coll){
            System.out.println(obj);
        }

        /*【foreach的工作过程:
        coll取第一个元素赋给Object obj,然后sout打印obj
        coll取第二个元素赋给Object obj,然后sout打印obj
        ...
        coll取最后一个元素赋给Object obj,然后sout打印obj

        怎么取的?
        debug看一下
        内部仍然调用了迭代器】
         */
    }

    @Test
    public void test2(){
        int[] arr = new int[]{1,2,3,4,5,6};
        //for(数组元素的类型 局部变量 : 数组对象)
        for(int i : arr){
            System.out.println(i);
        }
    }

    //练习题
    @Test
    public void test3(){

        String[] arr = new String[]{"MM","MM","MM"};

//        //方式一:普通for赋值
//        for(int i = 0;i < arr.length;i++){
//            arr[i] = "GG";
//        }

        //方式二:增强for循环
        for(String s : arr){
            s = "GG";
        }

        //测试
//        for(String s : arr){
            System.out.println("前" + s);


//            s = "GG";
            System.out.println("后" + s);
//        }

        for(int i = 0;i < arr.length;i++){
            System.out.println(arr[i]);
        }


    }
}

五、List接口

1.List接口常用实现类的对比

* 1. List接口框架
*      //单列数据
*    |----Collection接口:单列集合,用来存储一个一个的对象
*          |----List接口:存储有序的、可重复的数据。  -->“动态”数组,替换原有的数组
*              |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
*              //【基本上用这个,底层依然是数组,顺序存储。Array->数组】
*              //【超了长度,不用自己扩容,底层已经把方法写好了】
*              //【多线程,即使ArrayList是不安全的,也不用Vector,怎么解决呢?
*              //后面Collections工具类List synchronizedList(List<T> list),
*              //把ArrayList当作实参传进去,就可以返回一个线程安全的集合】
*
*              |----LinkedList:【对于频繁的插入、删除操作,使用此类效率比ArrayList高】;       底层使用双向链表存储
*              //【Link-->链表】
*              //【底层存储的数据结构不同,对数组元素的操作就不同】
*
*              |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[]  elementData存储
*              //效率低,所以不用。synchronized
*
*

2.ArrayList的源码分析

*   2.1 jdk 7情况下
*      ArrayList list = new ArrayList();//【底层创建了长度是10的Object[]数组elementData】
*      //【回顾:StringBuffer底层创建了一个长16的char[] value】
*
*      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的对象的创建类似于【单例的懒汉式】,【延迟了数组的创建,节省内存】。
*            //【jdk7,调构造器时候,ArrayList就在底层创建了长度是10的数组
*            //jdk8,调构造器时,创建了数组={},add的时候才让数组的长度变为10】
*            //【jdk8的更好。例如:后台造了个ArrayList,要往里面放数据,数据要在数据库中请求,请求需要时间,7已经造好了,8不提前造,节省内存】

3.LinkedList的源码分析

//jkd7、jkd8无区别
*      LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,
       默认值为null

*      list.add(123);//将123封装到Node中,创建了Node对象。
*
*      其中,Node定义为:体现了LinkedList的双向链表的说法
*      private static class Node<E> {
            E item;
            Node<E> next;
            Node<E> prev;

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

4.Vector的源码分析

*      jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
*      在扩容方面,默认扩容为原来的数组长度的2倍。

5.ArrayList、LinkedList、Vector三者的异同

*  同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
*  不同:见上
*  ArrayList和Vector比
*  ArrayList和LinkedList比:
*              ArrayList   LinkedList
*      查找      O(1)        O(n)
*      删除      O(n)        O(1)
*      插入      O(n)        O(1)

6.List接口中的常用方法

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位置的元素,并返回此元素
//【重载Collection接口中的remove(Object obj)。remove可以按照索引删,也可以按照对象删】

Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合

//【用数组的地方都可以替换成ArrayList。数组里定义的total相当于ArrayList里的size】

以上的方法都是基于index索引才有的

总结:常用方法:
增: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循环
     ③ 普通的循环
     //【因为有index索引了,所以也可以用普通的for循环】
public class ListTest {


    @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));//get(index)方法
        }



    }


    @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.
        //【java中index中找不到都返回-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位置的元素,并返回此元素
        //【ArrayList底层是数组,删除后,要把后面的元素往前移】
        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元素
        //【ArrayList底层是数组,添加前,要把索引后的元素都往后挪,再添加】
        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);
        //【add(list1)会把list1整体当成一个元素加入list,size = 6+1 = 7。集合类也是Object类】
        System.out.println(list.size());//9 = 6+3

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

    }


}

7.List遍历及方法总结

见6,test3

迭代器、foreach(增强for循环)、普通for循环

 

六、Set接口

1.Set接口实现类的对比

* 1. Set接口的框架:
*
* |----Collection接口:单列集合,用来存储一个一个的对象
*          |----Set接口:存储无序的不可重复的数据   -->高中讲的“集合”
*              |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
*                  |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的 顺序遍历
*                  //【这不叫有序,那什么是无序呢?】
*                  对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
*                  //【为频繁操作遍历准备的类】
*
*                  //【LinkedHashSet在HashSet的基础上加了前后的指针,或者说是链表,
*                  效果是看起来"有序",使得遍历LinkedHashSet的元素时,是按照添加时的顺序来的,就因为加了前后指针。但这个有序是骗人的,因为Set是无序的】
*
*              |----TreeSet:可以按照添加对象的指定属性,进行排序。
*              //底层用二叉树(红黑树)存的
*              //【之前List的实现类和HashSet类都可以随便放不同类型的对象,123,new Person,"haha"】
*              //要求可以排序,所以放入TreeSet的数据必须是同一个类new的。
*              //然后可以按照对象的某些属性排序。
*              //【Comparable、Comparator,然后调Arrays.sort或Collections.sort
*              //这两个接口在TreeSet体现】
*
*              //【为排序做准备的类】
*
*
* //【开发中用List和Map比较多,用Set比较少。Set和后面讲的Map有关,Map讲源码,Set自然懂了】
*
*  1. Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
*  //【List有序,有index索引,增加了很多和索引相关的方法。Set无序,没索引,所以没有新的方法】
*
*  2. 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
*     要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
*     //【怎么保持一致性?在hashCode()用到的属性,也要在equals中用】
*      重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。直接alt+shift+s生成最保险最方便

2.Set的无序性和不可重复性的理解

见3,元素添加过程。

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

//无序性并不是指遍历输出和添加时的顺序不同,以HashSet为例,
//【哈希值:hashCode()方法,Object类中的方法】

2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
//【记得重写equals,和hashCode仅重写equals,还是判断地址值是否相同】
//【用Set过滤重复数据,用的就是不可重复性

3.HashSet中元素的添加过程

添加元素的过程:以HashSet为例:
从添加元素的过程理解Set的无序性和不可重复性
//【这个理解有助于后面理解HashMap】
//【TreeSet和HashSet的添加元素过程不一样】
//很容易想到的方法:添加第一个元素,不用equals,因为是空的
//添加第二个元素,和第一个元素equals,false就放入HashSet容器里,true就不放入
//添加第三个元素,和第一个、第二个元素都要equals,false就放入,
//...
//添加第一千个元素(【底层是数组,也会扩容】),和前面999个元素equals,....
//这样效率很低,数越多,要遍历equals的次数也就越多,不靠谱。
//Set不是这样做的,
//7中,一创建Set,数组长度就指定为16了,8中,没有指定。
//它先考虑哈希值,调用所属类的hashCode()算哈希值(所以不止要重写equals还要重写hashCode()),用对象的属性算
//比如哈希值是13462,这个数就决定了对象在数组中的位置,不可能13462是索引,数组没那么长,所以对哈希值操作,算出真正的位置在哪
//假设操作的算法是和16取模,13462 % 16 = 0 ~ 15,取模结果就作为对象存在数组里的位置。

//Set的真实做法:
//1.添加第一个元素,算出哈希值,算出位置值,不用equals
//2.添加第二个元素,算出哈希值,算出位置值,先看位置上有没有元素,没有就直接放上去,不用equals
//3.添加第三个元素,算出哈希值,算出位置值,先看位置上有没有元素,没有就直接放上去,不用equals
//【不用equals比较,这就要求重写hashCode()和equals()时要一致】
//4.添加第四个元素,算出哈希值,算出位置值,先看位置上有没有元素,有元素(比如是第三个元素),
//此时,第三个元素,第四个元素,哈希值不一定一样,只是根据算法算出的位置值一样。比如1 % 16 = 1,17 % 16 = 1。
//如果哈希值不一样,则认为是不同的元素,就不用equals。
//那第四个元素放在哪?以链表的方式存放第三个和第四个元素。
//5.添加第五个元素,算出哈希值,算出位置值,先看位置上有没有元素,有元素(比如是第一个元素),
//而第五个元素和第一个元素的哈希值一样,不能直接说这两个元素一样,此时要用equals判断,
//用第五个元素的equals判断,把第一个元素作为实参传进去,第五个元素.equals(第一个元素),
//返回值是true,第五个元素就放不进容器了(哈希值一样,equals也一样),
//返回值是false,第五个元素就放进容器了(哈希值一样,但equals不一样),添加成功
//【上面这样做的好处:
//假设现在放第一千个元素:算哈希值,算位置值,位置上没有元素就直接放入,比自己想的方法高效,一个都不用比。
//位置上有元素(此时可能不止一个元素),再长也不会有999个,一个个比完,都不一样,那就存入,
//和某一个一样,那后面的就不用比了,添加失败】
视频总结的:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
    如果此位置上没有其他元素,则元素a添加成功。 --->添加成功情况1
    如果此位置上有其他元素b(或【以链表形式存在的多个元素】),则比较元素a与元素b的hash值:
        如果hash值不相同,则元素a添加成功。--->添加成功情况2
        如果hash值相同,进而需要调用元素a所在类的equals()方法:
        //【将元素b或以链表形式存在的多个元素通过循环的方式,往equals形参里放】
               equals()返回true,元素a添加失败
               equals()返回false,则元素a添加成功。--->添加成功情况3

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

//【添加元素过程:哈希值,算位置值->比哈希值->调equals】
//【以上的对讲HashMap非常有用,因为new HashSet(),其实new了HashMap()
//    public HashSet() {
//        map = new HashMap<>();
//    }
//
//add添加元素,添加到HashMap中了
//    public boolean add(E e) {
//        return map.put(e, PRESENT)==null;
//    }】
* HashMap添加元素过程和HashSet一样。Set底层就是Map
*
* 
*  Set : HashSet   LinkedHashSet   TreeSet
*  Map : HashMap   LinkedHashMap   TreeMap
*  new一个HashSet,底层new的是HashMap
*  new一个LinkedHashSet,底层new的是LinkedHashMap
*  new一个TreeSet,底层是TreeMap

Set的无序性理解:

 Set的无序性和不可重复性理解:

public class SetTest {

    @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));
        //【没重写User的equals和hashCode,遍历时就会有两个Tom,12
        // 没重写hashCode,调的是Object类里的hashCode,
        // public native int hashCode();   native说明调用的是底层的c语言。
        // 可以看作是随机计算哈希值,所以两个User对象哈希值不同,
        // 那算的位置值就不同,第二个User对象就直接放到一个空位置,比都不用比,所以连equals都没调,没输出"User equals()...."】
        set.add(129);

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


//        Set set = new HashSet();
//        set.add(456);
//        set.add(123);
//        set.add("AA");
//        set.add("CC");
//        set.add(new User("Tom",12));
//        set.add(129);
//
//        Iterator iterator = set.iterator();
//        while(iterator.hasNext()){
//            System.out.println(iterator.next());
//        }
        /*
        AA
        CC
        129
        456
        123
        User{name='Tom', age=12}
        输出和加入的顺序不同。【但这不叫无序性,LinkedHashSet遍历和添加时的顺序一样,能说是有序的吗?不能】
        无序性不是随机性
         */

    }

    //LinkedHashSet的使用
    //LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个
    //数据和后一个数据。
    //优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
    @Test
    public void test2(){
        Set set = new LinkedHashSet();
        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());
        }

        /*
        User equals()....
        456
        123
        AA
        CC
        User{name='Tom', age=12}
        129
       // 【遍历和添加时的顺序一样,能说LinkedHashSet是有序的吗?不能。(Set是无序、不可重复的)
       //无序性:指存放到数组位置上,是无序的,不是按顺序一个一个挨着放的。
       //那凭什么LinkedHashSet可以按照添加的顺序遍历?因为有Linked,有链表。课件p40】
         */
    }
}
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()....");//【Set:不可重复性。测试Set类的iterator有无调用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() {
        int result = name != null ? name.hashCode() : 0;
        //【String重写过hashCode(),不再是随机计算了】

        result = 31 * result + age;
        return result;
    }
    //return name.hashCode() + age;//【两个对象属性值不一样,哈希值也别一样;属性值一样,则哈希值一样】
    //【用这句话的坏处:
    // 对象a:name的哈希值24,age等于20;对象b:name的哈希值是20,age等于24.两个的属性值不同,
    // 但是最后return的值相同,调equals->false。指针连一下。
    // 也存上去了,但是觉得能少用指针就少用指针
    //
    // result = 31 * result + age;让result乘上一个数再加上age,这样最后结果相同的概率就比较低。
    // 为什么是31?2^5 - 1 = 32
    // 2 << 5 - 1】


    //2.按照姓名从大到小排列--->二级排序:按照姓名从大到小排列,年龄从小到大排列
    @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);//【保存compareTo的返回值】
            if (compare != 0){
                return compare;
            }else {
                return Integer.compare(this.age,user.age);//【this.age是int基本数据类型,没有方法】
            }
        }else {
            throw new RuntimeException("输入的类型不匹配");
        }
    }


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

4.关于hashCode()和equals()的重写

见3

5.LinkedHashSet的使用

见3

6.TreeSet的自然排序和定制排序

TreeSet的底层:

TreeSet和后面要讲的TreeMap采用红黑树的存储结构

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

3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0。不再调用equals().
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0。不再是调用equals().
import org.junit.Test;

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

public class TreeSetTest {

    @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);//【都是Integer类型】
//        set.add(-34);
//        set.add(43);
//        set.add(11);
//        set.add(8);

        /*
        -34
        8
        11
        34
        43
        【按从小到大遍历】
        //【包装类实现了Comparable接口,按照从小到大的顺序比较】
         */

        //举例二:
//        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));
        //【User没实现Comparable,没重写compareTo时,遍历报异常
        // java.lang.ClassCastException: com.atguigu.java1.User cannot be cast to java.lang.Comparable
        // 也就是说,TreeSet的iterator遍历调用了Comparable的compareTo,或Comparator的compare】


        //【举例三:】
        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));
        //【遍历结果:
        /*
        User{name='Tom', age=12}
        User{name='Mike', age=65}
        User{name='Jim', age=2}
        User{name='Jerry', age=32}
        User{name='Jack', age=33}
        */
        // 按照Set的添加过程,new User("Jack",33)和new User("Jack",56),是不一样的两个元素,
        // 但compareTo 比较这两个对象,是一样的,return了0】
        //【Set里其他的实现类(HashSet和LinkedHashSet)都是用equals比较是否相同,
        // TreeSet用compareTo比较元素是否相同,所以要完善仅用姓名比较的compareTo,
        // 二级排序】


        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);
        //【没传Comparator,就把元素用从小到大的逻辑(实现Comparable)放进TreeSet的底层结构红黑树中,
        // 传了Comparator,就用自己写的Comparator逻辑add元素】

        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());
        }
    }

}

七、Map接口

1.Map接口及其多个实现类的对比

* 一、Map的实现类的结构:
*  |----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
*         |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
*         //【和ArrayList一样,即使涉及到多线程问题,也不会因为线程安全而用Hashtable,
*         而是用Collections工具类中的相关方法,把HashMap变成安全的】
*
*              |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
*                      原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和  后一个元素。
*                      对于频繁的遍历操作,此类执行效率高于HashMap。
*
*         |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
*                      //排序是按照key来排的
*                      底层使用红黑树
*                      //和HashMap、LinkedHashMap、Hashtable底层结构不同
*
*         |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
*              |----Properties:常用来处理配置文件。key和value都是String类型
* //出现时间:Hashtable存键值对 -> Map -> HashMap -> TreeMap存有序键值对
*                                       HashMap遍历效率太差 -> LinkedHashMap
* //Vector -> List -> ArrayList -> LinkedList
* //Set -> HashSet -> TreeSet
*                  -> LinkedHashSet
*
*
*      HashMap的底层:数组+链表  (jdk7及之前)
*                    数组+链表+红黑树 (jdk 8)
*  面试题:
*  1. HashMap的底层实现原理?
*  2. HashMap 和 Hashtable的异同?
*  3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
*  //不论如何,把HashMap用工具类转成安全的之后,同步代码块里面都是单线程的,
*  为了让高并发的时候,执行效率更高,引入CurrentHashMap

transient讲序列化的时候再说

2.对Map中key、value的理解

*  //对Map的key、value的理解:
*    Map中的key:无序的、不可重复的,使用Set存储所有的key  ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
*    //用什么Set存,取决于是什么Map:HashMap就用HashSet存,LinkedHashMap就用LinkedHashSet存
*    //自定义类重写equals()和hashCode()保证存入的数据是不可重复的
*    //如果是TreeMap,就要重写compareTo或compare保证数据不重复
*
*    Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
*    一个键值对:key-value构成了一个entry对象。
*    //【key、value可以看作是是对象entry的属性】
*
*    Map中的Entry:无序的、不可重复的,使用Set存储所有的Entry

3.HashMap在jdk7中的底层实现原理

HashMap的底层实现原理?以jdk7为例说明:
*      HashMap map = new HashMap():
*      在实例化以后,底层创建了长度是16的一维数组Entry[] table。//put的数据都放在数组中
*      ...可能已经执行过多次put...
*      map.put(key1,value1)://【把key和value放入数组中,怎么放?以下是过程】
*      首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
*      如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
*      //实质上是Entry1添加成功
*
*      如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
*              如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
*              //要和链表上所有的entry比哈希值
*              如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
*                      如果equals()返回false:此时key1-value1添加成功。----情况3
*                      如果equals()返回true:使用value1替换value2。
*                      //【put("Tom",23);   key是Tom,value是23
*                      //put("Tom",45);
*                      //"Tom"和"Tom"的哈希值相同,equals返回true,遍历map结果是Tom 45】
*  //【总结:1.算哈希值 --> 算位置值
*  //2.位置为null,直接添加
*  //3.位置不为null,比哈希值 --> 3.1哈希值不同,添加为链表
*                           --> 3.2哈希值相同,(比地址值)比equals,equals返回false,添加为链表,equals返回true,替换】
*       补充:关于情况2和情况3:此时key1-value1和原来的数据以【链表】的方式存储。
*       //【jdk7源码:】
*       //【void addEntry(int hash, K key, V value, int bucketIndex) {
                if ((size >= threshold) && (null != table[bucketIndex])) {
                resize(2 * table.length);
                hash = (null != key) ? hash(key) : 0;
                bucketIndex = indexFor(hash, table.length);
            }//【这一块逻辑体现的是扩容】

                createEntry(hash, key, value, bucketIndex);//【不扩容,就直接添加】
           }
*
*          void createEntry(int hash, K key, V value, int bucketIndex) {
                Entry<K,V> e = table[bucketIndex];//【①先取出位置上原有的元素,】
                table[bucketIndex] = new Entry<>(hash, key, value, e);
                   //【②new新的Entry,放新添加的元素】
                   //【④把新造的Entry放入位置上】
                size++;
           }

           Entry(int h, K k, V v, Entry<K,V> n) {
                value = v;
                next = n;//【③原有的元素作为当前对象的next】
                key = k;
                hash = h;
           }
       】
*      //【jdk8源码:】
*      //【public V put(K key, V value) {
                return putVal(hash(key), key, value, false, true);
       // }
       //【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)
               //【首次put的逻辑】
               //【数组为空,就帮忙造一个数组】
    //            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;
    //            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                   //【比哈希值:哈希值相等,比地址值,地址值一样就不用比equals了,比equals,返回true】
                   //【这里只是比了位置上第一个元素的哈希值】
    //                e = p;//【把p存到e里,还没有替换】

                   //【能到这来,说明和位置上第一个元素哈希值不等或者哈希值相等equals返回false】
                   //【还要和链表上其他元素比较】
    //            else if (p instanceof TreeNode)
    //                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    //            else {
    //                for (int binCount = 0; ; ++binCount) {
                       //【for循环:位置上可能不止一个元素,可能是链表,所以要和链表上所有的元素比】
    //                    if ((e = p.next) == null) {//【p.next == null,说明这个位置上只有一个元素p】
    //                        p.next = newNode(hash, key, value, null);//【把新造的key-value作为p的next,这就是七上八下的八下】
    //                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st //【这一块是涉及什么时候链表变成红黑树】
                               //【当添加的链表长度超过8时,】
    //                            treeifyBin(tab, hash);//【就把链表变成红黑树】
    //                        break;
    //                    }
                           //【p.next不为null,说明p后面还有元素】
                           //【和链表上所有的Node比较哈希值,比较地址值,比较equals】
    //                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
    //                        break;//【哈希值一样,equals一样,就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;
//    }】

   final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
           resize();//【数组长度<64,不把链表变成树,而是扩容数组,因为树结构还是比数组复杂的,直接扩容更方便】
        else if ((e = tab[index = (n - 1) & hash]) != null) {
           TreeNode<K,V> hd = null, tl = null;
           do {
               TreeNode<K,V> p = replacementTreeNode(e, null);
               if (tl == null)
                   hd = p;
               else {
                   p.prev = tl;
                   tl.next = p;
               }
               tl = p;
           } while ((e = e.next) != null);
           if ((tab[index] = hd) != null)
               hd.treeify(tab);
        }
   }
*

 

*      在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置【非空】)时,扩容。
*      默认的扩容方式:扩容为原来容量的【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
*      //【ArrayList底层一开始容量是10,存前十个元素都不会扩容,到第11个才扩容
*      //HashMap底层一开始是16,不是到第17个才扩容,而是【提前扩容】
*      //为什么要提前扩容,不能满了再扩吗?它一定会满吗?怎样才叫满?
*      //因为位置值是通过哈希值算的,而且还有链表,有些位置可能始终都不会有元素。
*      //提前扩容:让数组里出现链表的情况尽可能少
*      //想让链表出现的少,让加载因子尽可能小
*      //假设加载因子=0.3,16x0.3=4.8,超过4个就要扩容,对数组的利用率就低了,所以太小也不好,
*      //太大了,链表就多了,也不太好。
*      //大量测试0.75兼并了对数组利用率高+链表少】
*
*      threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
*      //【扩容和threshold有关,扩容不是等到快装满了才扩容】
*
*      TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
*      MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

4.HashMap在jdk8中的底层实现原理

见3

jdk7:数组+链表

jdk8:数组+链表+红黑树

 

 

5.HashMap在jdk7中的源码分析

见3

6.HashMap在jdk8中的源码分析

见3

7.LinkedHashMap的底层实现

*  //【LinkedHashMap是HashMap的子类,底层使用的结构和HashMap相同,LinkedHashMap存储时还是用HashMap里定义的数组】
*  //【区别在于LinkedHashMap内部定义了一个Entry,替换了HashMap中的Node】
*  //【LinkedHashMap可以按照添加的顺序遍历】
*  //【把LinkedHashMap底层实现原理搞明白了,LinkedHashSet也就懂了】
* //【LinkedHashMap里的添加方法,用的是父类的,put调putVal
* final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict),
* 但它有自己重写的newNode
*     Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }】

*      源码中:
*      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);
            }
        }
* 【HashSet与HashMap
* 1.new一个HashSet,本质是new一个HashMap
*     public HashSet() {
        map = new HashMap<>();
      }
 2.向HashSet中add元素时,相当于把元素放入Map。
   放入Set中的数据相当于Map中的key,
   Map中的value是PRESENT,private static final Object PRESENT = new Object();一个static final常量,不是null
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
】

8.Map中的常用方法1

添加、删除、修改操作:
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是否相等

9.Map中的常用方法2

    元视图操作的方法:
   //【Map的遍历:如何遍历Map的key、value、key和value】
   //【Collection的遍历:迭代器、foreach、普通for循环。Map没有迭代器】
   //【Map是由key和value构成的,所有的key是一个Set,拿到所有的key构成的Set,用Set的迭代器就行了,
   //拿到所有的value构成的Collection,用Collection的迭代器就行了
   //键值对整体构成一个Set,拿到所有键值对构成的Set】
    Set keySet():返回所有key构成的Set集合
    Collection values():返回所有value构成的Collection集合
    Set entrySet():返回所有key-value对构成的Set集合

//【遍历并不是为了看Map里的元素,如果仅仅是为了看,直接System.out.println(map);
//用户注册,要填很多信息,name-编辑框、age-编辑框、学历-编辑框等等,
//name、age、学历都可以作为key,编辑框填的内容就是value。
//填完后,把数据发送到后台,后台用Map装,Map里面都是键值对,
//把Map数据装入数据库,数据库有很多字段:name、age、学历,
//把Map中每一个key和value取出放入数据库,一个一个取的过程就是遍历。
//把数据在后台,把数据转化为JSON呈现到浏览器】

*总结:常用方法:
* 添加:put(Object key,Object value)
* 删除:remove(Object key)
* 修改:put(Object key,Object value)
* 查询:get(Object key)
* 长度:size()
* 遍历:keySet() / values() / entrySet()
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);
        }
        /*
        AA
        BB
        45

        123
        56
        1234
         */
        System.out.println();



        //遍历所有的key-value
        //方式一:entrySet()
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            Object obj = iterator1.next();//【obj就是一个一个的entry】
            //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是否相等
 //【要想结果为true,obj也必须是Map,且内容一样】
     */
    @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");//【判断哈希值->判断equals】
        System.out.println(isExist);

        isExist = map.containsValue(123);//【有两个123,找到是true就不会再往下找了】
        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);//【实际开发中,key的类型是统一的,value的类型是统一的。到泛型再讲。key是String的情况比较多。】
        map.put(45,123);
        map.put("BB",56);
        //【修改/替换】
        //【"AA"哈希值相同,value不同,修改】
        map.put("AA",87);//【Map算哈希值,是算key的哈希值】

        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操作不同
        //【是让Map里的元素都为null,而不是让Map指向null】
        System.out.println(map.size());//【不会空指针异常】//0
        System.out.println(map);//【{}】
    }

//    LinkedHashMap可以按照添加的顺序遍历,能记录添加的顺序
    @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能存null的应用场景:
        // 在浏览器登录账号name,密码password,点登录,
        // 数据保存在map中,怎么保存?
        // 两个键值对。
        //                  key         value
        // 第一个键值对:    name         小明
        // 第二个键值对:    password     1234567,
        //然后在数据库中校验
        // 如果用户忘了写,那value就是null,在Hashtable中就会导致空指针异常】
    }
}

10.TreeMap的两种添加方式

public class TreeMapTest {

    //向TreeMap中添加key-value,要求key必须是由【同一个类】创建的对象
    //因为要【按照key进行排序】:自然排序 、定制排序
    //【不能按照value排序】
    //自然排序
    @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);//【开发中,key、value一般都是固定类型,讲泛型再强调这件事】
        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());

        }
    }


}
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("输入的类型不匹配");
        }

    }
}

11.Properties处理属性文件

public class PropertiesTest {

    //Properties:常用来处理配置文件。【key和value都是String类型】
    //【配置文件:物理上存在的文件。用Properties把配置文件读到内存中
    // 比如修改了idea的配置信息,怎么在idea运行中体现这些修改了?idea的指令读配置文件。
    // java中,用Properties读配置文件】

    //【idea配置文件,默认在工程下,右键new File(name写jdbc.properties)
    // 或Resource Bundle(写jdbc,自动补充.properties),
    // (后面讲java操作数据库的技术,就是jdbc)。
    // jdbc.properties写name不要name = Tom,会认为name是" Tom"而不是"Tom"】
    public static void main(String[] args)  {
        FileInputStream fis = null;
        try {
            Properties pros = new Properties();

            fis = new FileInputStream("jdbc.properties");//【流要有关闭操作】
            pros.load(fis);//加载【流】对应的文件

            String name = pros.getProperty("name");
            String password = pros.getProperty("password");

            System.out.println("name = " + name + ", password = " + password);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

    }
}

12.Collections工具类常用方法

* xxx
* xxxs:工具类;xxx不能造对象,通过xxxs调方法返回一个xxx类型的对象;
* xxx Factory调方法返回一个xxx类型的对象
* Collections:操作Collection、【Map】的工具类
/*
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中
//【    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
            int srcSize = src.size();
            if (srcSize > dest.size())
                throw new IndexOutOfBoundsException("Source does not fit in dest");

            if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
            } else {
                ListIterator<? super T> di=dest.listIterator();
                ListIterator<? extends T> si=src.listIterator();
                for (int i=0; i<srcSize; i++) {
                    di.next();
                    di.set(si.next());
                }
            }
       }】
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值

 */
public class CollectionsTest {


        //返回的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);

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值