集合

集合

集合概述

  1. 什么是集合?有什么用?

    数组其实就是一个集合,集合其实是一种容器。可以容纳其他类型的数据。

    为什么集合在开发中使用较多?

    ​ 集合是一种容器,是一个载体,可以一次容纳多个对象。

    ​ 在实际开发中,假设连接数据库,数据库有10条数据,那么假设查询这10条数据,

    ​ Java程序会将这10条数据封装成10个对象,然后将10个Java对象放到一个集合当中,

    ​ 由集合传到前端,然后遍历集合,将数据展现出来。

  2. 集合不能直接储存基本数据类型,另外集合也不能直接储存Java对象,集合中储存的都是Java对象的地址

    或者说集合储存的是引用。

    注意:

    list.add(100);//自动装箱

    ​ 集合在Java中本身就是一个容器,一个对象。

    ​ 在任何情况下储存的都是引用。

  3. 在Java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中储存元素,等于将数据放到不同的数据结构当中。

    什么是数据结构?数据存储的结构就是数据结构,不同的数据结构数据储存方式不同。例如:

    ​ 数组、二叉树、哈希表、链表....这些都是常见的数据结构。

    ​ 向c1集合中放数据,可能放到了数组上。

    ​ 向c2集合中放数据,可能放到了二叉树上。

    ​ ......

    ​ 使用不同的集合想当于使用了不同的数据结构。

    在Java中掌握的不是精通数据结构,Java中已经将数据结构实现了,写好了常用的集合类,只需要掌握去用,选择合适的集合使用即可。

    new ArrayList();创建一个集合对象,底层是数组。

    new LinkedList();创建一个集合对象,底层是链表。

    new TreeSet();创建一个集合对象,底层是二叉树。

    ....

  4. 集合在Java JDK中那个包下?

    java.util.*

    ​ 所有的集合类和集合接口都在这个包下。

  5. 为了熟练掌握这一章节,最好将集合的继承结构背会!

    对于集合这个体系是一个什么样的结构,要有个大致印象。

  6. 在Java中集合分为两大类:

    一类是单个方式储存元素:

    ​ 单个方式储存元素,这一类集合中超级父接口:java.util.Collection;

    一类是以键值对的方式储存元素

    ​ 以键值对的方式储存元素,这一类集合中超级父接口:java.utli.map;

  7. 总结:(所有的实现类)

    ArrayList:底层是数组

    LinkedList:底层是双向链表

    HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合的key部分

    TreeSet:底层是HashMap,放到TreeSet集合中的元素等同于放到HashMap集合的key部分

    HashMap:底层是哈希表

    Hashtable:底层也是哈希表(线程安全,效率低,使用较少)

    Properties:是线程安全的,且key和value只能存储字符串

    TreeMap:底层是二叉树,TreeMap集合的key可以按照大小顺序排序

List集合储存元素的特点:

​ 有序可重复

​ 有序:存进去的顺序和取出来的顺序相同,每个元素都有下标

​ 可重复:允许储存相同的元素

Set集合储存元素的特点:

​ 无序不可重复

​ 无序:存进去的顺序和取出来的顺序,不一定相同,另外Set元素集合没有下标

​ 不可重复:不允许储存相同的元素

SortedSet集合储存元素的特点:

​ 首先是无序不可重复,但是SortedSet集合中的元素是可排序的

​ 可排序:可以按照大小顺序排列

Map集合的key,就是一个Set集合

​ 往Set集合中放数据,实际上放到Map集合中的Key部分

集合继承结构图

Collection接口常用方法

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

/*
关于java.util.Collection接口中常用的方法
    1. Collection能存放什么元素?
        没有使用"泛型"之前,Collection中可以储存Object的所有子类型。
        使用"泛型"之后,Collection中只能储存某个具体的类型。
        集合后期我们会学习"泛型"语法,目前先不管,Collection中什么都能存。
        只要是Object的的子类型就行。(集合中不能直接储存基本数据类型,也不能存储Java对象,储存的是内存地址。)
    2. Collection中的常用方法
        boolean	add(E e)    向集合中添加元素
        int	size()          获取集合中元素的个数
        void clear()        清空集合
        boolean	contains(Object o)  判断当前集合中是否含有元素o,包含返回ture,不包含返回false。
        boolean	remove(Object o)    删除集合中的某个元素。
        boolean isEmpty()           判断该集合是否为空。
        Object[] toArray()          调用这个方法可以将集合转化为数组。
 */
public class CollectionTest {
    public static void main(String[] args) {
        //创建一个集合对象
        //Collection c = new Collection();接口是抽象的,无法实例化。
        //多态
        Collection c = new ArrayList();
        //测试Collection接口中的常用方法
        c.add(1200);//自动装箱,实际上是放进去一个对象的内存地址,等同于 Integer x = new Integer(1200);c.add(x);
        c.add(3.14);//自动装箱
        c.add(new Object());
        c.add(new Student());
        c.add(true);//自动装箱

        //获取集合中元素的个数
        System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是5

        //清空集合
        c.clear();
        System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是0

        //添加元素
        c.add("Hello");
        c.add("World");
        c.add("绿巨人");
        c.add("浩克");
        c.add(1);

        //判断集合中是够包含"绿巨人"
        System.out.println(c.contains("绿巨人"));//true
        System.out.println(c.contains("绿巨人1"));//false
        System.out.println(c.contains(1));//true

        System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是5

        //删除集合中某个元素
        c.remove(1);
        System.out.println("集合中元素的个数是" + c.size());//集合中元素的个数是4

        //判断集合是否为空
        System.out.println(c.isEmpty());//false
        c.clear();
        System.out.println(c.isEmpty());//true

        c.add("Hello");
        c.add("World");
        c.add("绿巨人");
        c.add("浩克");
        c.add(1);
        c.add(new Student());

        //转换为数组
        Object[] objs = c.toArray();
        for (int i = 0; i < objs.length; i++) {
            System.out.println(objs[i]);
            /*
            Hello
            World
            绿巨人
            浩克
            1
            Student@6b884d57
             */
        }
    }
}

class Student {

}

Collection集合迭代/遍历

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

/*
关于集合迭代/遍历专题
 */
public class CollectionTest01 {
    public static void main(String[] args) {
        //注意:以下讲解的遍历/迭代方式,是所有Collection通用的一种方法。
        //在Map集合中不能用,在所有Collection以及子类中使用。
        //创建集合对象
        Collection c = new HashSet();//后面的集合无所谓,主要是看前面的Collection接口,怎么迭代/遍历。
        //添加元素
        c.add("abc");
        c.add("def");
        c.add(100);
        c.add(new Object());
        //对集合Collection进行迭代/遍历
        //第一步:获取集合对象的迭代器对象Iterator
        Iterator it = c.iterator();
        //第二步:通过以上获取的迭代器对象开始迭代/遍历集合
        /*
        以下两个方法
            boolean	hasNext() 如果仍有元素可以迭代,则返回ture。
            Object next()     返回迭代的下一个元素。
         */
        while (it.hasNext()) {
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class CollectionTest02 {
    public static void main(String[] args) {
        //创建集合对象
        Collection c = new ArrayList();
        //添加元素
        c.add(1);
        c.add(2);
        c.add(3);
        c.add(4);

        //迭代集合
        Iterator it =c.iterator();
        while (it.hasNext()) {
            //存进去什么类型,取出来就是什么类型。
            Object obj = it.next();
            if (obj instanceof Integer) {
                System.out.println("Integer类型");
            }
            //只不过在输出的时候会转换为字符串
            System.out.println(obj);
          /*
          Integer类型
          1
          Integer类型
          2
          Integer类型
          3
          Integer类型
          4
          */
        }

        //HashSet集合:无序不可重复
        Collection c1 = new HashSet();
        //无序:存进去和取出来的顺序,不一定相同。
        //不可重复:存进去100就不能再存100。
        c1.add(100);
        c1.add(100);
        c1.add(200);
        c1.add(300);
        c1.add(400);
        c1.add(500);
        c1.add(600);
        /*
        400
        100
        500
        200
        600
        300
        */

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

    }
}

contains方法

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

/*
深入了解Collection集合的contains方法:
    boolean	contains(Object o)
        判断集合中是否包含某个对象o
        如果包含返回true,不包含返回false。

    contains方法是用来判断集合中是否包含某个元素的方法,它在底层调用了equal方法进行比对。
 */
public class CollectionTest03 {
    public static void main(String[] args) {
        //创建集合对象
        Collection c = new ArrayList();

        //向集合中储存元素
        String s1 = new String("abc");
        c.add(s1);

        String s2 = new String("def");
        c.add(s2);

        System.out.println("元素的个数为" + c.size());

        String s3 = new String("abc");

        System.out.println(c.contains(s3));//true
    }
}

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

public class CollectionTest04 {
    public static void main(String[] args) {
        //创建集合对象
        Collection c = new ArrayList();
        //创建用户对象
        User u1 = new User("jack");
        User u2 = new User("jack");
        //加入集合
        c.add(u1);

        //判断集合中是否包含u2
        //没有重写equals之前这个是false,重写之后就变为了true
        //重写之后就比较内容了
        System.out.println(c.contains(u2));//true
        //其实底层相当于u2.equals(u1);
        c.remove(u2);
        //其实底层相当于u2.equals(u1);
        System.out.println(c.size());//0

    }
}

class User {
    private String name;

    public User() {
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name);
    }
}

关于集合中元素的删除

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

public class CollectionTest05 {
    public static void main(String[] args) {
        //创建集合
        Collection c = new ArrayList();
        //添加元素
        c.add(1);
        c.add(2);
        c.add(3);
        
        //获取迭代器
        Iterator it = c.iterator();

        while (it.hasNext()) {
            //编写代码时next()方法返回值必须是Object。
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

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

/*
关于集合元素的remove
    重点:当集合结构发生改变时,迭代器必须重新获取,如果还在使用以前的迭代器会出现异常:java.util.ConcurrentModificationException

    重点:在迭代元素的过程中,不能调用集合对象的remove()方法 删除元素:c.remove(o);
         迭代过程中不能这样,会出现:java.util.ConcurrentModificationException

    重点:获取迭代器对象遍历集合,相当于对当前集合拍了一个快照,迭代器会按照当前快照进行迭代。
         直接通过集合去删除元素,没有通知迭代器,导致快照和集合状态不同,迭代器去删除时,会自动更新迭代器和集合。
         在迭代元素中,一定要使用迭代器Iterator的remove方法,删除元素,不要使用集合自带的remove方法删除元素。
 */
public class CollectionTest05 {
    public static void main(String[] args) {
        //创建集合
        Collection c = new ArrayList();
        //注意:此时获取的迭代器,指向的是那个集合中没有元素状态的迭代器。
        //一定要注意:只要集合结构发生改变,迭代器必须重新获取。
        //当集合结构发生了改变,迭代器没有重新获取,调用next()方法时:java.util.ConcurrentModificationException
        Iterator it = c.iterator();

        //添加元素
        c.add(1);
        c.add(2);
        c.add(3);

        while (it.hasNext()) {
            //编写代码时next()方法返回值必须是Object。
            Object obj = it.next();
            it.remove();//删除的一定是迭代器指向的当前元素
            System.out.println(obj);
        }
    }
}

List接口特有方法

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
测试List接口常用方法
    1. List集合储存元素的特点:有序可重复
        有序:List集合每个元素都有下标,从0开始,以1递增。
        可重复:允许储存相同的元素
    2. List既然是Collection的子接口,那么List接口肯定有自己的方法
        以下只列出List接口特有且常用方法:
            void add(int index, E element)
            E get(int index)
            int	indexOf(Object o)
            int	lastIndexOf(Object o)
            E remove(int index)
            E set(int index, E element)
        以上方法不需要死记硬背,自己多试理解
        以后开发还是要翻阅文档
 */
public class ListTest01 {
    public static void main(String[] args) {
        //创建List类型的集合
        List myList = new ArrayList();

        //添加元素
        myList.add("A");//默认都是向集合末尾添加元素
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        //将指定元素插入此列表中的指定位置(第一个数是下标)
        //这个方法使用的不多,因为对于ArrayList集合来说效率比较低。
        myList.add(1,"KING");

        //迭代
        Iterator it = myList.iterator();
        while (it.hasNext()) {
            Object obj = it.next();
            System.out.println(obj);
        }

        //根据下标获取元素
        Object firstObj = myList.get(0);
        System.out.println(firstObj);

        //因为有下标,所以List集合有自己比较特殊的遍历方式
        //通过下标遍历
        for (int i = 0; i < myList.size(); i++) {
            Object object = myList.get(i);
            System.out.println(object);
        }

        //获取指定对象第一次出现处的索引
        System.out.println(myList.indexOf("KING"));//1

        ///获取指定对象最后一次出现处的索引
        System.out.println(myList.lastIndexOf("C"));//4

        //删除指定下标位置的元素
        //删除下标为0的元素
        myList.remove(0);
        System.out.println(myList.size());//5

        //修改指定位置的元素
        myList.set(2,"Soft");

        //遍历集合
        for (int i = 0; i < myList.size(); i++) {
            Object object = myList.get(i);
            System.out.println(object);
        }
    }
}

ArrayList

ArrayList集合

  1. 默认初始化容量是10

  2. 集合底层是一个Object[] 数组

  3. 构造方法:

    构造器 描述
    ArrayList() 构造一个初始容量为10的空列表。
    ArrayList(int initialCapacity) 构造具有指定初始容量的空列表。
    ArrayList(Collection<? extends E> c) 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.List;
    
    public class ArrayList01 {
        public static void main(String[] args) {
            
            //创建一个HashSet集合
            Collection c = new HashSet();
            c.add(1);
            c.add(2);
            c.add(3);
            c.add(4);
            c.add(5);
            c.add(6);
            c.add(7);
            c.add(8);
            c.add(9);
            c.add(10);
    
            //通过这个构造方法就可以将HashSet集合转换为List集合
            List list = new ArrayList(c);
    
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
        }
    
    }
    
  4. ArrayList集合的扩容:

    原容量的1.5倍

LinkedList

链表的优点:

​ 由于链表上的元素,在空间储存上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率极高,在以后的开发中,如果遇到随机增删集合中元素的业务较多时,建议使用LInkedList

链表的缺点:

​ 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止,所以查找的效率较低。

ArrayList:将检索发挥到了极致

LinkedList:把随机增删发挥到了极致

但是一般加元素都是向末尾添加,所以ArrayList用的多

import java.util.LinkedList;
import java.util.List;

public class LinkedListTest01 {
    public static void main(String[] args) {
        List list = new LinkedList();
        list.add("a");
        list.add("b");
        list.add("c");

        for (int i = 0; i <list.size(); i++) {
            System.out.println(list.get(i));
        }

        //LinkedList集合有初始化容量吗?没有。
        //最初这个链表什么元素都没有,first和last都是null
        //不管是ArrayList还是LinkedList,以后写代码不需要关心使用那个集合
        //因为我们要面向接口编程,调用的方法都是接口的方法。
        
    }
}

Vector

import java.util.*;

/*
1. 底层是一个数组
2. 初始化容量为10
3. 每扩容一次容量就是原来的二倍
4. ArrayList每次扩容增加1.5倍
5.Vector是线程安全的,但是效率低
6. 怎么将一个线程不安全的ArrayListj集合转换为线程安全的呢?
    使用集合工具类
        java.util.Collections;

        java.util.Collection 是集合接口
        java.util.Collections 是集合工具类
7.
 */
public class VectorTest01 {
    public static void main(String[] args) {

        Vector vector = new Vector();

        vector.add(1);
        vector.add(2);
        vector.add(3);
        vector.add(4);
        vector.add(5);
        vector.add(6);
        vector.add(7);
        vector.add(8);
        vector.add(9);
        vector.add(10);

        //满了自动扩容,容量变为20
        vector.add(11);

        //这个可能以后会使用
        List myList = new ArrayList();
        //变成线程安全的
        Collections.synchronizedList(myList);//这里没办法看效果,没学多线程。

        myList.add(111);
        myList.add(222);
        myList.add(333);
    }
}

泛型

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
JDK5.0之后推出的新特性:泛型
    泛型这种语法机制,只在程序编译的时候起作用,只是给编译器参考的,运行阶段泛型没用!
使用泛型的好处是:
    1. 集合储存的元素类型统一
    2. 取出的事指定类型,不需要进行大量的向下转型
泛型的缺点是:
    集合中储存的元素缺乏多样性
    大多数业务中,集合中元素类型还是统一的,所以这种泛型特性被大家认可
 */
public class GenericTest01 {
    public static void main(String[] args) {
        /*
        List myList = new ArrayList();

        //准备对象
        Cat c = new Cat();
        Bird b = new Bird();

        //将对象添加到集合当中
        myList.add(c);
        myList.add(b);

        //遍历集合 取出每个Animal 让他Move
        Iterator it =myList.iterator();
        while(it.hasNext()) {
            Object obj = it.next();
            Animal a = (Animal)obj;
            a.move();
        }
        */
        //使用JDK5的泛型机制
        //使用泛型List<Animal>之后,表明List集合中只允许存储Animal类型的数据。
        //用泛型来指定集合中储存的数据类型。
        List<Animal> myList = new ArrayList<Animal>();

        //指定List集合中只能储存Animal,那么储存String就报错。
        //这样使用泛型过后,集合中的元素类型更加统一。
        //myList.add("123");

        Cat c = new Cat();
        Bird b = new Bird();

        myList.add(c);
        myList.add(b);

        //获取迭代器
        //这个表示迭代器迭代的是Animal对象
        Iterator<Animal> it = myList.iterator();
        while (it.hasNext()) {
            //使用泛型之后,每一次迭代返回的数据都是Animal类型
            //Animal a = it.next();
            //这里不需要强制类型转换,直接调用
            //a.move();

            //调用子类特有的方法还是需要向下转型
            Animal a = it.next();
            if (a instanceof Cat) {
                Cat cat = (Cat)a;
                cat.catchMouse();
            }
            if (a instanceof Bird) {
                Bird bird = (Bird) a;
                bird.fly();
            }
        }
    }
}

class Animal {
    //父类方法
    public void move() {
        System.out.println("动物在移动");
    }
}

class Cat extends Animal {
    //特有方法
    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}

class Bird extends Animal {
    //特有方法
    public void fly() {
        System.out.println("鸟在飞翔");
    }
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
JDk8之后引入了:自动类型推断(又称为钻石表达式)
 */
public class GenericTest02 {
    public static void main(String[] args) {
        //ArrayList<这里的类型会自动推断>() ,前提是JDK8之后允许。
        //自动类型判断,钻石表达式
        List<Animal> myList = new ArrayList<>();

        myList.add(new Animal());
        myList.add(new Cat());
        myList.add(new Bird());

        Iterator<Animal> it = myList.iterator();
        while (it.hasNext()) {
            //这样迭代器取出的都是Animal类型
            Animal a = it.next();
            //这里不需要强制类型转换,直接调用
            a.move();
        }

    }
}

/*
自定义泛型可以吗?可以
    自定义泛型的时候,<> 尖括号中是一个标识符,随便写。
    Java源代码经常出现的是:
    <E>和<T>
    E是Element单词首字母
    T是Type单词首字母
 */
public class GenericTest03<标识符随便写> {

    public void doSome(标识符随便写 o) {
        System.out.println(o);
    }

    public static void main(String[] args) {

        //new对象是时指定了泛型是:String类型
        GenericTest03<String> gt = new GenericTest03<>();

        //类型不匹配
        //gt.doSome(100);

        gt.doSome("abc");
    }
}

增强for循环(foreach)


/*
JDK5。0之后推出了新特性:叫增强for循环,或者叫Foreach
 */
public class ForEachTest01 {
    public static void main(String[] args) {
        //int类型数组
        int[] arr = {1,2,3,4,5,6};

        //遍历数组(普通for循环)
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        //增强for循环(foreach)
        //以下是语法
        /*
        for (元素类型 变量名 : 数组或者集合) {
            System.out.println(变量名);
        }
         */

        //foreach有个缺点就是没有下标,在需要使用下标的循环,不需要使用。
        for (int data : arr) {
            //data就是数组的每一个元素
            System.out.println(data);
        }
    }
}

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
集合使用增强for
 */
public class ForEachTest02 {
    public static void main(String[] args) {
        //创建List集合
        List<String> strList = new ArrayList<>();
        //添加元素
        strList.add("Hello");
        strList.add("World");
        strList.add("kitty");
        //遍历,使用迭代器迭代
        Iterator<String> it = strList.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        //使用下标方式(只针对有下标的集合)
        for (int i = 0; i < strList.size(); i++) {
            System.out.println(strList.get(i));
        }

        //使用foreach
        for (String s : strList) {
            System.out.println(s); 
        }
    }
}

Map

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/*
java.util.Map接口中常用方法:
    1. Map和Collection没有继承关系
    2. Map集合以key和value的方式存储数据:键值对
        key和value都是引用数据类型,都是存储对象的内存地址,key起到主导作用,value是key的附属品。
    3.Map接口中常用方法:
        V put(K key, V value)                  向Map集合中添加键值对
        V get(Object key)                      通过key获取value
        void clear()                           清空Map集合
        boolean	containsKey(Object key)        判断Map中是否包含某个key
        boolean	containsValue(Object value)    判断Map中是否包含某个value
        boolean	isEmpty()                      判断Map集合中元素是否为0
        V remove(Object key)                   通过key删除键值对
        int	size()                             获取Map集合中键值对的个数
        Collection<V> values()                 获取Map集合中所有的value ,返回一个Collection
        Set<Map.Entry<K,V>>	entrySet()         将Map集合转换为Set集合
        Set<K>	keySet()                       获取集合所有的key

        常用:Set<Map.Entry<K,V>>	entrySet()         将Map集合转换为Set集合
            假设现在有个Map集合,如下所示:
                map集合对象
        key             value
        -------------------------------
        1               zhanhsan
        2               lisi
        3               wangwu
        4               zhaoliu

        Set set = map.entrySet();
        set集合对象
        1=zhanhsan
        2=lisi
        3=wangwu
        4=zhaoliu
        注意:Map集合通过entrySet()方法转换成这个Set集合,Set集合中元素的类型是Map.Entry<K,V>
        Map.Entry和String一个样,都是一个类型的名字,只不过:Map.Entry是静态内部类。


 */
public class MapTest01 {
    public static void main(String[] args) {

        //创建Map集合对象
        Map<Integer,String> map = new HashMap<>();

        //向Map集合中添加键值对
        map.put(1,"zhanhsan");//1在这里进行了自动装箱
        map.put(2,"lisi");
        map.put(3,"wangwu");
        map.put(4,"zhaoliu");

        //通过key获取value
        System.out.println(map.get(2));//lisi

        //获取键值对的数量
        System.out.println("键值对的数量为:" + map.size());//键值对的数量为:4

        //通过key删除key-value
        map.remove(2);
        System.out.println("键值对的数量为:" + map.size());

        //判断是否包含某个key
        //contains方法底层都是调用equals方法进行比对,所以自定义的类型需要重写equals方法
        System.out.println(map.containsKey(4));//true

        //判断是否包含某个value
        System.out.println(map.containsValue("wangwu"));//true

        //获取所有的value
        Collection<String> values = map.values();
        //foreach 
        for (String value : values) {
            System.out.println(value);
        }

        //清空map集合
        map.clear();
        System.out.println("键值对的数量为:" + map.size());//键值对的数量为:0

        //判断是否为空
        System.out.println(map.isEmpty());//true

    }
}

import java.util.*;

/*
Map集合的遍历(非常重要)
 */
public class MapTest02 {
    public static void main(String[] args) {
        //第一种方式:获取所有的key,通过遍历key来遍历value
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"zhanhsan");
        map.put(2,"lisi");
        map.put(3,"wangwu");
        map.put(4,"zhaoliu");
        //遍历map集合
        //获取所有的key,所有的key是一个Set集合
        Set<Integer> keys = map.keySet();
        //遍历key,通过key获取value
        //迭代器可以
        Iterator<Integer> it = keys.iterator();
        while (it.hasNext()) {
            //取出其中其中的一个key
            Integer key = it.next();
            //通过key获取value
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }
        //foreach也可以
        for (Integer key : keys) {
            System.out.println(key + "=" + map.get(key));
        }

        //第二种方法:Set<Map.Entry<K,V>>	entrySet()
        //以上这个方法是把Map集合直接转换为Set集合
        //Set集合中元素的类型是:Map.Entry
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        //遍历Set集合,每次取出一个Node
        //迭代器
        Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while (it2.hasNext()) {
            Map.Entry<Integer,String> node = it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + "=" + value);
        }

        //foreach
        //这种方式效率较高,因为获取key和value都是直接从node对象中获取的属性值
        //这种方式适合大数据量
        for (Map.Entry<Integer, String> node : set) {
            System.out.println(node.getKey() + "=" + node.getValue());
        }
    }
}

HashMap

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/*
HashMap集合:
    1. HashMap集合底层是哈希表/散列表的数据结构
    2. 哈希表是一个怎么样的数据结构
        哈希表是一个数组和单向链表的结合体
        数组:在查询的效率很高,在随机增删效率很低
        单向链表:在随机增删效率很高,在查询的效率很低
        哈希表将以上两种数据结构融合在了一起,充分发挥它们各自的优点
    3. HashMap集合底层的源代码:
        public class HashMap {

        //HashMap底层其实是一个数组(一维数组)
        transient Node<K,V>[] table;

        //静态的内部类HashMap.node
            static class Node<K,V> implements Map.Entry<K,V> {
                final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果,hash值可以通过哈希函数/算法,可以转换储存成数组的下标)
                final K key;//存储到Map集合中的那个Key
                V value;//存储到Map集合中的那个value
                Node<K,V> next;//下一个节点的内存地址
            }
        }
    4. 哈希表/散列表:一维数组,这个数组每一个元素都是一个单向链表(数组和链表的结合体)
    5. 最主要掌握的是:
        map.put(k.v)
        v = map.get(k)
        map.put(k.v)实现原理:
            第一步:先将k,v封装到Node对象中
            第二步:底层会调用k的hashCode()方法得出hash值,然后通过哈希算法/哈希函数,将哈希值转换为数组的下标,下标位置如果没有任何元素
                   就将Node添加到这个位置上,如果下标对应的位置有链表,此时会将此时会拿着k和链表上的每一个节点z中的k进行equals
                   如果所有的节点进行equals返回的都是false,那这个新节点将会添加到末尾,如果有一个节点equals返回的是true
                   那么这个节点的value将会被覆盖
        v = map.get(k)实现原理:
                   先调用k的hashCode()方法得出哈希值,通过哈希算法转换为数组下标,通过数组下表快速定位到某个位子上,如果什么都没有,返回null
                   如果这个位置上有单向链表,那么会拿着参数k进行equals,如果所有的节点进行equals都返回false。那么get()方法返回null
                   只要有其中的任意一个节点equals返回的是是true,那么这个节点就是我们要找的value,那么get()方法返回找到的value
        注意:同一个单向链表上所有的节点hash值都相同,因为数组下标都一样,但是同一个链表上的k和k的equals方法肯定是返回的是false都不相等
             通过上面的解释可以知道HashMap集合的key会先后调用两个方法,一个是hashCode()方法,一个是equals()方法,这两个方法都要重写
        以上这两个方法的实现原理必须掌握
    6. HashMap集合的key特点:
        无序不可重复
        为什么无序?因为不一定挂到那个单向链表上
        不可重复是如何保证的?equals方法保证HashMap集合的key不可重复
        如果key重复了,value会覆盖
        放在HashMap集合的key其实就是放到HashSet集合中
        所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法
    7. 哈希表HashMap使用不当无法发挥性能
        假设所有的hashCode()方法返回值是一个固定的值,那么对导致底层哈希表变成纯单向链表
        这种情况我们称为:散列分布不均匀
        什么是散列分布均匀?
        假设有100个元素,10个单向链表,每个链表上有10个节点,这就是散列均匀分布
        假设所有的hashCode()方法返回值都不一样,那哈希表就变为了一维数组,没有了链表的概念,这也是散列分布不均匀
        散列分布均匀需要你重写hashCode()方法时有一定的技巧
    8. 重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要重写hashCode和equals方法
    9. HashMap集合的默认初始化容量为16,默认加载因子是0.75
       这个默认加载因子是当HashMap集合底层数组容量达到%75的时候,就会扩容,容量是之前的2倍
       重点:记住HashMap集合初始化容量必须是2的倍数,这是官方推荐,因为这是达到散列分布均匀,提高HashMap集合的存取效率,所必须的
    */
public class HashMapTest {
    public static void main(String[] args) {
        //测试hashMap集合key元素的特点
        //Integer是key,它的hashCode和equals都被重写了
        Map<Integer,String> map = new HashMap<>();
        map.put(1111,"张三");
        map.put(2222,"李四");
        map.put(3333,"王五");
        map.put(4444,"赵六");
        map.put(4444,"Han");//key重复时,value会自动覆盖

        System.out.println(map.size());//4

        //遍历
        Set<Map.Entry<Integer, String>> set = map.entrySet();
        for (Map.Entry<Integer, String> entry : set) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

import javase.bean.Student;

import java.util.HashSet;
import java.util.Set;

/*
1. 向Map集合中存和取,都是先调用key的hashCode方法再调用equals方法
    equals方法有可能调用,有可能不调用
        拿put(k,v)举例,什么时候equals不调用?
            数组为空
        拿get(k)举例,什么时候equals不调用?
            数组为空
2. 注意:如果一个类的equals方法重写,那么hashCode方法必须重写,并且equals方法返回的如果是true,那么HashCode返回值必须是一样的
3. hashCode方法和equals方法不用手写,IDEA能自动生成,但是这两个方法必须同时生成
4. 放在HashMap集合的key部分和放在HashSet集合中的元素,需要重写hashCode方法和equals方法
5. 在JDK8之后如果哈希表单向链表中元素超过8个,单向链表会变成红黑树这种数据结构,当红黑树上的节点少于6个时,会将红黑树变成单向链表数据结构
   这种方式也是为了提高检索效率
6. 对于哈希表数据结构来说:
        如果o1和o2d的hash值相同,一定是放到同一个单向链表上的
        如果o1和o2d的hash值步同,但是有可能哈希算法执行结束后转换的数组下标相同,此时会发生"哈希碰撞"
 */
public class HashMapTest01 {
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan");
        Student s2 = new Student("zhangsan");

        //重写equals方法之前是false
        //System.out.println(s1.equals(s2));false

        //重写equals方法之后是ture
        System.out.println(s1.equals(s2));//true

        System.out.println("s1的hashCode=" + s1.hashCode());//951007336(重写之后:-1432604525)
        System.out.println("s2的hashCode=" + s2.hashCode());//603742814(重写之后:-1432604525)

        //但是经过重写后的s1.equals(s2)已经是true了,表示s1和s2是一样的,那么向HashSet集合中放元素,按道理只能放一个
        //但是它们的hashCode结果却不一样,表示它们不一样,违背了不可重的原则!
        Set<Student> students = new HashSet<>();
        students.add(s1);
        students.add(s2);
        System.out.println(students.size());//2 这个按道理来说应该是1,因为里面储存的都是"zhangsan",显然不符合HashSet集合储存特点

    }
}
package javase.bean;

import java.util.Objects;

public class Student {
    private String name;

    public String getName() {
        return name;
    }

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

    public Student() {
    }

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

    //hashCode
    //equals(如果学生名字一样,表示同一名学生)
    /*
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Student)) return false;
        if (this == obj) return true;
        Student s = (Student) obj;
        return name.equals(this.name);
    }
     */

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

import java.util.HashMap;
import java.util.Map;

/*
HashMap集合允许null吗?允许
 */
public class HashMapTest02 {
    public static void main(String[] args) {

        Map map = new HashMap();

        //HashMap集合允许key为null
        map.put(null,null);
        System.out.println(map.size());//1

        //key重复value会覆盖
        map.put(null,100);
        System.out.println(map.size());//1

        //通过key获取value
        System.out.println(map.get(null));//100
    }
}

import java.util.Hashtable;
import java.util.Map;

/*
HashMap集合允许null吗?不允许

HashTable集合是线程安全的,因为都有synchronized关键词修饰
导致效率较低,使用较少

HashTable和HashMap一样,底层都是哈希表数据结构
Hashtable的初始化容量为11,默认加载因子是0.75F
Hashtable的扩容是:原容量 * 2 + 1
 */
public class HashTableTest {
    public static void main(String[] args) {
        Map map = new Hashtable();

        //map.put(null,"123");
        //java.lang.NullPointerException

        //map.put(1,null);
        //java.lang.NullPointerException
    }
}

Properties

import java.util.Properties;
/*
目前只需要掌握Properties属性类对象的相关方法即可
Properties是一个Map集合,继承HashTable,properties的key和value都是String类型
Properties被称为属性类
Properties是线程安全的
 */
public class PropertiesTest {
    public static void main(String[] args) {
        //创建一个Properties对象
        Properties pro = new Properties();

        //掌握Properties的两个方法,一个存,一个取
        pro.setProperty("url","jdbc:mysql://localhost:3306/Han");
        pro.setProperty("driver","com.mysql.jdbc.Driver");
        pro.setProperty("username","admin");
        pro.setProperty("password","123456");

        //通过key来获取value
        String url = pro.getProperty("url");
        String driver = pro.getProperty("driver");
        String username = pro.getProperty("username");
        String password = pro.getProperty("password");

        System.out.println(url);
        System.out.println(driver);
        System.out.println(username);
        System.out.println(password);

    }
}

TreeSet

import java.util.TreeSet;

/*
1. TreeSet集合底层其实是一个TreeMap
2. TreeMap集合底层是一个二叉树
3. 放到TreeSet集合中的元素,等同于放到TreeMap的key部分
4. TreeSet集合中的元素:无序不可重复,但会按照大小自动排序(可排序集合)
5.
 */
public class TreeSetTest {
    public static void main(String[] args) {
        //创建一个TreeSet集合
        TreeSet<String> ts =  new TreeSet<>();
        //添加String
        ts.add("a");
        ts.add("d");
        ts.add("q");
        ts.add("f");
        ts.add("k");
        ts.add("s");
        ts.add("b");
        //遍历
        for (String s : ts) {
            //按照字典顺序排列,升序
            System.out.println(s);
        }

        TreeSet<Integer> ts1 =  new TreeSet<>();
        ts1.add(100);
        ts1.add(90);
        ts1.add(10);
        ts1.add(120);
        ts1.add(60);
        ts1.add(30);

        for (Integer i : ts1) {
            //按照字典顺序排列,升序
            System.out.println(i);
        }
    }
}
/*
数据库有很多数据:
    userId    name    birth
    ----------------------------------
    1         zs       1980-11-11
    2         ls       1980-10-11
    3         ww       1981-11-11
    4         zl       1979-11-11

编写程序从数据库取出数据,在页面显示用户信息的时候按照生日升序或者降序
这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来是有顺序的
 */
import java.util.TreeSet;

/*
对于自定义的类型,TreeSet可以排序吗?
    以下程序对于Person类型来说,无法排序,因为没有指定Person对象的比较规则
    没有说明谁大谁小

    所以程序运行的时候出现了以下的异常:
        Exception in thread "main" java.lang.ClassCastException: class Person cannot be cast to class java.lang.Comparable
        (Person is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
    出现这个的原因是:
        Person类没有实现java.lang.Comparable接口

 */
public class TreeSetTest01 {
    public static void main(String[] args) {
        Person p1 = new Person(32);
        Person p2 = new Person(20);
        Person p3 = new Person(30);
        Person p4 = new Person(25);

        //创建TreeSet集合对象
        TreeSet<Person> persons = new TreeSet<>();
        //添加元素
        persons.add(p1);
        persons.add(p2);
        persons.add(p3);
        persons.add(p4);
        //遍历
        for (Person p : persons) {
        System.out.println(p);
        }
    }
}

class Person {
    private int age;

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

    public int getAge() {
        return age;
    }

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

    public Person() {
    }

    @Override
    public String toString() {
        return "Person[age=" + age + "]";
    }
}
import java.util.TreeSet;

public class TreeSetTest02 {
    public static void main(String[] args) {
        Customer c1 = new Customer(32);
        Customer c2 = new Customer(20);
        Customer c3 = new Customer(30);
        Customer c4 = new Customer(25);

        //创建TreeSet集合对象
        TreeSet<Customer> customers = new TreeSet<>();
        //添加元素
        customers.add(c1);
        customers.add(c2);
        customers.add(c3);
        customers.add(c4);
        //遍历
        for (Customer c : customers) {
            System.out.println(c);
            /*
            Customer[age=20]
            Customer[age=25]
            Customer[age=30]
            Customer[age=32]
             */
        }
    }
}

//放在TreeSet集合中的元素需要实现java.lang.Comparable接口
//并且是想compareTo方法,equals可以不写
class Customer implements Comparable<Customer> {
    private int age;

    public Customer(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

    public Customer() {
    }

    @Override
    public String toString() {
        return "Customer[age=" + age + "]";
    }
    //需要在这个方法中编写比较的逻辑,或者比较的规则,按照什么进行比较!
    //k.compareTo(t.key)
    //拿着参数k和集合中的每一个k进行比较,返回值可能是 >0 <0 =0
    //比较规则由程序员指定例如:按年龄升序/降序
    @Override
    public int compareTo(Customer c) { //c1.compareTo(c2);
        //this是c1
        //c是c2
        //c1和c2比较时就是this和c的比较
        /*
        int age1 = this.age;
        int age2 = c.age;
        if (age1 == age2) {
            return 0;
        } else if (age1 > age2) {
            return 1;
        } else {
            return -1;
        }
         */
        return this.age - c.age;// >0 <0 =0
    }

}

import java.util.TreeSet;

/*
先按照年龄升序,如果年龄一样再按照姓名升序
 */
public class TreeSetTest03 {
    public static void main(String[] args) {
        TreeSet<Vip> vips = new TreeSet<>();
        vips.add(new Vip("张三",20));
        vips.add(new Vip("张四",20));
        vips.add(new Vip("李四",18));
        vips.add(new Vip("王五",17));
        for (Vip vip: vips) {
            System.out.println(vip);
        }
    }
}

class Vip implements Comparable<Vip>{
    private String name;
    private int age;

    public Vip() {
    }

    public Vip(String name) {
        this.name = name;
    }

    public Vip(int age) {
        this.age = age;
    }

    public Vip(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 "Vip{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    /*
    compareTo的返回值很重要:
        返回0表示相同,value被覆盖
        返回>0,会继续在右子树上找
        返回<0,会继续在左子树上找
     */
    @Override
    public int compareTo(Vip v) {
        if (this.age == v.age) {
            //年龄相同时,按照姓名排序
            //姓名是String类型,可以直接比,调用compareTo方法比较
            return this.name.compareTo(v.name);
        } else {
            return this.age - v.age;
        }
    }
}


自平衡二叉树

  1. TreeSet/TreeMap是自平衡二叉树,遵循左小右大原则存放

  2. 遍历二叉树的三种方法:

    前序遍历:根左右

    中序遍历:左根右

    后序遍历:左右根

    注意:

    前中后说的是根的位置

    根在前面是前序

    根在中间是中序

    根在后面是后序

  3. TreeSet/TreeMap是集合采用的是:中序遍历方式(左根右)

实现比较器接口

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

/*
TreeSet集合中元素排序的第二种方式:使用比较器的方式
结论:
    第一种:放在集合的元素实现java.lang.Comparable接口
    第二种:在构造TreeSet或者TreeMap集合的时候给他传个比较器对象
那么Comparable和Comparator怎么选择?
    当比较规则不会发生改变的时候,或者当比较规则只有一个的时候,建议实现Comparable
    如果比较规则有多个,并且需要多个比较规则来回切换,建议使用Comparator接口
    Comparator接口的设计是符合OCP原则
 */
public class TreeSetTest04 {
    public static void main(String[] args) {
        //创建TreeSet这个集合的时候,需要使用这个比较器
        //TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传入一个比较器

        //给构造方法传递一个比较器
        TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());

        //我们也可以采用匿名内部类的方式
        /*
        TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.getAge() - o2.getAge();
            }
        });
         */

        wuGuis.add(new WuGui(1000));
        wuGuis.add(new WuGui(800));
        wuGuis.add(new WuGui(810));

        for (WuGui wuGui : wuGuis) {
            System.out.println(wuGui);
        }
    }
}

//乌龟
class WuGui {

    private int age;

    public WuGui() {
    }

    public WuGui(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "小乌龟[" +
                "age=" + age +
                ']';
    }
}

//单独在这里编写一个比较器
//比较器实现java.lang.Comparator接口(Comparable是java.lang包下的,Comparator是java.util包下的)
class WuGuiComparator implements Comparator<WuGui> {

    @Override
    public int compare(WuGui o1, WuGui o2) {
        //指定比较规则
        //按照年龄排序
        return o1.getAge() - o2.getAge();
    }
}

Collections

import java.util.*;

/*
java.util.Collection    集合接口
java.util.Collections   集合工具类,方便集合的操作
 */
public class CollectionsTest {
    public static void main(String[] args) {
        //ArrayList不是线程安全的
        List<String> list = new ArrayList<>();
        //变成线程安全的
        Collections.synchronizedList(list);
        
        //排序
        list.add("abd");
        list.add("abc");
        list.add("abx");
        list.add("abz");

        Collections.sort(list);
        for (String lists : list) {
            System.out.println(lists);
        }

        List<WuGui2> wuGuis = new ArrayList<>();
        wuGuis.add(new WuGui2(1000));
        wuGuis.add(new WuGui2(8000));
        wuGuis.add(new WuGui2(800));
        //注意:对List集合中的元素排序,需保证List集合中的元素实现了Comparable接口
        Collections.sort(wuGuis);
        for (WuGui2 wg : wuGuis) {
            System.out.println(wg);
        }

        //对Set集合怎么排序?
        //Set---->List
        Set<String> set = new HashSet<>();
        set.add("1");
        set.add("2");
        set.add("24");
        set.add("3");
        set.add("0");
        List<String> myList = new ArrayList<>(set);
        Collections.sort(myList);
        for (String s : myList) {
            System.out.println(s);
        }

        //这种排序也可以  
        //Collections.sort(List集合,比较器对象);
    }
}

class WuGui2 implements Comparable<WuGui2> {

    private int age;

    public WuGui2() {
    }

    public WuGui2(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public int compareTo(WuGui2 w) {
        return this.age - w.age;
    }

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

posted @ 2021-02-07 01:21  HanWDragon  阅读(45)  评论(0)    收藏  举报