菜鸟笔记
提升您的技术认知

java之集合

数据结构

含义:就是在计算机的缓存,内存,硬盘如何组织管理数据的;重点是在结构上,是按照什么结构来管理我们的数据的

数据结构分类:

  • 逻辑结构:思想上的结构->线性表(数组、链表)、图、树、栈、队列
  • 物理结构:逻辑上的结构->紧密结构(顺序结构)、跳转结构(链式结构)

逻辑结构和物理结构关系

  • 线性表的逻辑结构,对应的真实结构如果是紧密结构,那么典型例子就是数组
  • 线性表的逻辑结构,对应的真实结构如果是跳转结构,那么典型例子就是链表 

紧密结构优缺点

  • 优点:寻址快(查找元素快)
  • 缺点:增删慢

跳转结构优缺点

  • 优点:增删快
  • 缺点:查询慢

线性表

含义:线性表是n个类型相同数据元素的有限序列。

线性表的逻辑结构

线性表的特点

  • 相同数据类型:线性表的每个数据元素是具有相同的属性的元素
  • 序列(顺序性):除了表头和表尾之间的所有元素都有且仅有一个直接前驱和直接后继
  • 有限:线性表中数据元素的个数n是一个有限值

链表

含义:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表分类

  • 单向链表:链表中的每个数据都保存着数据与下一个元素的地址
  • 双向链表:链表中的每个元素都保存着数据与上一个元素地址以及下一个元素的地址,首元素的上一个地址为null,尾地址的下一个地址为null
  • 循环链表:链表中的每个元素都保存着数据与上一个元素地址以及下一个元素的地址,首元素上一个元素地址为尾元素地址,尾元素的下一个地址为首元素地址

单向链表

双向链表

循环链表

含义:树是(n>=0)个节点的有限集合,n=0时称为空树

任何一个非空树的满足条件

  1. 有且仅有一个特定的称为根的节点
  2. 当n>1时,其余节点可分为m(m>0)个互不相交的有限集合,T1,T2,T3……Tm,其中,每个集合本身又是一棵树,称为根节点的子树 

注意:

  • 树是一种递归的数据结构
  • 除了根节点以外的所有节点,有且仅有1个前驱节点
  • 树中的所有节点可以有0或多个后继节点 

树的分析

 

根节点:A

祖先节点:A,B,D都为I的祖先节点

子孙节点:B,D,I,K为A的子孙节点

双亲节点:A为B的双亲节点,也为C的双亲节点

孙子节点:B为A的孙子节点

兄弟节点:有相同双亲的节点(如B和C)

树中的基本概念

节点的度:树中的1个节点的子节点个数(如A的度为2,B的度为3)

树的度:树中节点的最大度(如上面树的度为3)

分支节点:度大于0的节点

叶子节点:度为0的节点

节点的层次:从根节点开始定义,根节点为第一次,他的子节点为第二层,以此类推

节点的深度:从根节点开始,自顶向下逐层累加

节点的高度:从叶子节点开始从底向上逐层累加

树的高度:树中节点的最大层数

有序树:树中节点的子树从左到右是有次序的

无序树:树中节点的子树从左到右没有次序,可以交换

路径:树中两个节点的路径是由这两个节点之间所经过的节点序列构成

路径的长度:路径上所经过边的个数

二叉树

含义:二叉树又称二叉查找树,亦称二叉搜索树,二叉树(binary tree)是指树中结点的度不大于2的有序树,它是一种最简单且最重要的树。

二叉树的特性:任意一个左子树的节点值都比当前节点的值小,任意一个右子树的节点值都比当前节点值大

二叉树的遍历

  • 先序遍历:根节点——>左子树——>右子树(A,B,D,C,E,G,F)
  • 中序遍历:左子树——>根节点——>右子树(B,D,A,E,G,C,F)
  • 后序遍历:左子树——>右子树——>根节点(D,B,G,E,F,C,A)

集合

注意:数组和集合都是对数据进行存储操作的,简称容器,但这里的存储指的是内存层面的存储,而不是持久化的存储

数组特点:

  • 数组一旦创建,长度不可改变
  • 数组中可以存放引用数据类型与基本数据类型
  • 一旦声明了类型以后,数组中只能存放这个类型的数据。数组中只能存放同一种类型的数据
  • 删除,增加元素效率低
  • 数组只能获取数组整个长度,实际元素数量没有办法获取,没有提供对应的方法或者属性来获取
  • 数组储存:有序、可重复,对于无序,不可重复的数据,数组不能满足要求

集合特点:

  • 只能存放引用类型数据,不能存放基本数据类型数据
  • 集合的长度是可变的
  • 集合可以存放不同数据类型

集合结构图

继承结构特点

List接口:数据有下标,有序,可重复

  • ArrayList类:底层维护的是数组——紧密结构
  • LinkedList类:底层维护的是链表——跳转结构(虚拟下标)

Set接口:数据无下标,无序,不可重复

  • HashSet类:底层HashMap
  • TreeSet类:底层TreeMap

Map接口:键值对的方式存数据

  • HashMap类:底层数组+链表(哈希表)
  • TreeMap类:底层二叉树

Collection接口

常用方法

clear():移除此集合中的所有元素,没有返回值

equals(Object o):判断此集合对象与指定对象是否相等,相等返回true,否则为false

        Collection c = new ArrayList<>();
        c.add(18);
        c.add(34);
        c.add(18);
        List<Integer> list = Arrays.asList(new Integer[]{1,4,9});
        c.addAll(list);//将另一个集合加入该集合中
        c.remove(18);
        System.out.println(c);
        System.out.println("集合是否包含18这个元素"+c.contains(18));
        System.out.println("集合的长度为"+c.size());
        c.clear();//清空集合所有元素
        System.out.println("集合是否为空"+c.isEmpty());
        System.out.println(c.toString());
        Collection c1=new ArrayList<>();
        System.out.println("c和c1集合内容是否一样"+c.equals(c1));//判断集合是否一样

集合的遍历

        Collection collection = new ArrayList<>();
        collection.add(18);
        collection.add(34);
        collection.add("18");
       //对集合进行遍历
        //增强for循环
        for(Object o:collection){
            System.out.println(o);
        }

        //迭代器遍历
        Iterator iterator = collection.iterator();
        //如果有下一个元素就返回true,否则返回false
        while (iterator.hasNext()){
            //打印该(下一个)元素,并且指针下移一位
            System.out.println(iterator.next());
        }

List接口

List接口:数据有下标,有序,可重复

常用方法

remove(int index):根据下标移除列表中指定位置的元素, 返回值为移除的元素

注意:可以看到list扩展的方法都和索引相关 

        //集合下标从0开始
        List list=new ArrayList();
        list.add(13);
        list.add(17);
        list.add(true);
        list.add(2);
        list.add(1,89);//在下标为1的位置增加89
        list.set(3, 66);//将下标为3位置的元素改为66
        //注意:list的remove有2个方法,分别传入索引与Object类型
        list.remove(2);//索引的优先级高,如果索引和元素恰好一样,那么会把索引下的元素删除。
        list.remove((Object)2);//删除为2的元素
        System.out.println(list);
        System.out.println("下标为0的元素"+list.get(0));

List集合的遍历

        //list集合的遍历
        //for循环
        for (int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        }

        //利用迭代器(这里我打算给下标为1以及之后的元素进行迭代)
        ListIterator listIterator = list.listIterator(1);
        //正序迭代
        while (listIterator.hasNext()){
            System.out.println(listIterator.next());
        }

注意:ListIterator迭代器可以进行逆序迭代

ArrayList类

创建对象:ArrayList arrayList=new ArrayList([初始容量]);

注意:

  • ArrayList类重写了toString方法,重写后的toString方法能具体表示集合中的元素
  • ArrayList类重写了equals方法,重写后的equals方法比较的是集合内容是否相等
  • jdk1.8的ArrayList类底层维护的是Object类型的数组,在调用构造器的时候,底层数组为空,在调用add方法以后,底层数组才重新赋值为新数组,新数组的长度为10,节省了内存,当容量不够用的时候,底层会以1.5倍的容量增长

ArrayList与Vector差别

  • ArrayList底层扩容长度为原数组的1.5倍,Vector底层扩容长度为原数组的两倍
  • ArrayList线程不去安全,效率高;而Vector的每个方法都加了同步关键字,线程安全,但效率低
  • ArrayList调用方法时才会初始化数组长度,Vertor在调用构造方法时就初始化数组长度(两者初始化数组长度都为10

LinkedList类

创建对象:LinkedList<Object> linkedList = new LinkedList<>();

常用方法

增加

addFirst(E e):将指定元素插入到此列表的开头,返回值为void

addLast(E e):将指定元素插入到此列表的结尾,返回值为void

offer(E e):将指定元素添加到此列表的末尾,成功返回true,否则返回false

offerFirst(E e):在此列表的开头插入指定元素,成功返回true,否则返回false

offerLast(E e):在此列表末尾插入指定元素,成功返回true,否则返回false

删除

poll():获取并移除此列表的头

pollFirst():获取并移除此列表的第一个元素,若列表中没元素则返回null

pollLast():获取并移除此列表的最后一个元素,若列表中没元素则返回null

removeFirst():移除并返回此列表的第一个元素,若列表中没元素则抛异常

removeLast():移除并返回此列表的最后一个元素,若列表中没元素则抛异常

查看

element():获取但不移除此列表的第一个元素,返回第一个元素

getFirst():返回此列表的第一个元素,若列表中没元素则抛异常

getLast():返回此列表中的最后一个元素,若列表中没元素则抛异常

indexOf(Object o):返回此列表中首次出现的指定元素的索引,如果此列表中不包含该素,则返回-1

lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回-1

peek():获取但不移除此列表的头

peekFirst():获取但不移除此列表的第一个元素,若此列表为空,则返回null

peekLast():获取但不移除此列表的最后一个元素,若此列表为空,则返回null

LinkedList总结

  • LinkedList底层使用的链表为双向链表
  • LinkedList链表中,首先将需要放入的元素封装为对象,对象中有3个属性,第一个属性为前一个元素的地址,第二个属性为数据部分,第三个属性为后一个元素的地址,整个这个对象才是链表中的一个节点
  • LinkedList链表查询速度,查询中间元素速度小于查询两边元素速度

Iterator迭代器

hasNext和next方法

public class ArrayList<E> {
    //底层的Object类型数组
    transient Object[] elementData;
    //数组的长度
    private int size;
    private class Itr implements Iterator<E>{
        int cursor;
        int lastRet=-1;
        public boolean hasNext() {
            //cursor和size相等则返回false,否则返回true
            return cursor!=size;
        }
        @Override
        public E next() {
            int i=cursor;
            //获取局部变量数组
            Object[] elementData = ArrayList.this.elementData;
            cursor=i+1;
            return (E) elementData[lastRet-i];
        }
    }
}

iterator方法

        ArrayList<String> l = new ArrayList<>();
        l.add("a");
        l.add("b");
        l.add("c");
        System.out.println(l);
        //遍历
        Iterator<String> iterator = l.iterator();
        //查看集合有没有下一个元素,有则返回true,否则返回false
        while (iterator.hasNext()){
            //打印下一元素,并将指针下移一位
            System.out.println(iterator.next());
        }

listIterator方法

        ArrayList<String> l = new ArrayList<>();
        l.add("a");
        l.add("b");
        l.add("c");
        l.add("d");
        System.out.println(l);
        //正序遍历(遍历索引1以及之后的元素,当然也可以不传,那么就是遍历所有元素)
        ListIterator<String> lt = l.listIterator(1);
        while (lt.hasNext()){
            System.out.println(lt.next());
        }
        System.out.println(lt);
        System.out.println(lt.hasNext());
        //如今指针已经指向最后了,可以逆序遍历
        //判断是否有前一个元素,如果有返回true,否则false
        while (lt.hasPrevious()){
            //打印前一个元素,指针上移一位
            System.out.println(lt.previous());
        }

迭代器总结

  1. Collection接口继承了Iterable接口,Iterable接口里面有iterator方法
  2. iterator()为抽象方法,该抽象方法要在具体的实现类中实现,而方法的返回值为Iterator接口
  3. 在Iterator接口里有两个经典方法:hasNext(),next();
  4. hasNext(),next()两个为抽象方法,这两种抽象方法在Itr类中得到具体实现,但Itr类为ArrayList类的内部类
  5. listIterator可以实现逆序遍历
  6. 增强for循环的底层也是通过迭代器实现

Set接口

set接口:数据无下标,无序,不可重复(如果第二次add同样的数据,那么第二次的数据不会放入集合)

注意:set接口里没有与索引相关的方法

HashSet类

创建对象:HashSet<Object> hs = new HashSet<>([初始容量],[加载因子]);

        HashSet<Integer> hs = new HashSet<>();
        hs.add(19);
        hs.add(8);
        hs.add(19);
        System.out.println(hs.size());//结果为2
        System.out.println(hs);//打印是乱序的

注意:

  • 自定义的引用类型必须重写hashCode和equals函数才能实现HashSet的去重,原因请见HashSet原理
  • HashSet的去重原理就是利用HashMap的key(将存的数据放入HashMap的key中)
  • HashSet底层就是HashMap,因此,初始容量和加载因子以及扩容方式参照HashMap

HashSet原理

向hashSet集合中存入数据(以Integer类型为例)

注意:

  • hashSet底层原理:数组+链表
  • 通过hashCode函数来计算出关键字key
  • key通过哈希函数算出存放入数组的具体下标
  • hashSet集合中存放数据时,会对存放的数据进行比对(调用内部的equals方法),看是否该元素在集合中是否已经存在,若存在则不会对该元素进行处理
  • 哈希函数是根据关键字设计的,有很多种函数,主要原理就是根据数组大小求模运算(关键字%数组大小)最后求得的余数放入数组中某个下标的位置
  • 哈希冲突:通过key和哈希函数计算出的数组下标一样,解决方式——链表
  • 对于哈希冲突,jdk1.8采用尾插法(后来的元素插到链表的后面)

LinkedHashSet类

  • LinkedHashSet继承自HashSet
  • LinkedHashSet集合元素有序,不可重复(就相当于有序的HashSet),其会按照输入顺序进行输出
  • LinkedHashSet相对于HashSet来说就多了一个总的链表,这总的链表将放入的元素都串在一起(第一个存放的数据作为链表的第一个数据,最后一个存放的数据作为链表最后一个数据)

比较器

String的内部比较器

comparable接口有compareTo方法,这个compareTo方法就是用来完成比较的方法

public interface Comparable<T> {
    public int compareTo(T o);
}

String类实现了这个接口,重写了该方法,如下

    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

理解:由上面源码可知String在比较两个字符串的时候对两个字符串的每个字符逐个比较,如果在比较过程中出现了不等情况,那么将会返回第一次出现不相等情况下字符1与字符2的ASCII码值差值,如果两个字符串前面字符都一样,只是长度不相同,那么将会返回字符串1的长度与字符串2长度的差值,差值为0代表两个字符串相等

        String s1="abc";
        String s2="abc";
        System.out.println(s1.compareTo(s2));

比较自定义类型

内部比较器

含义:实现了Comparable接口,所有比较的逻辑全部都在自己即将比较的类的内部进行实现

public class Student implements Comparable<Student>{
    private int age;
    private double height;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public double getHeight() {
        return height;
    }
    public void setHeight(double height) {
        this.height = height;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student(int age, double height, String name) {
        this.age = age;
        this.height = height;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", height=" + height +
                ", name='" + name + '\'' +
                '}';
    }
    //比较两个学生的年龄
    @Override
    public int compareTo(Student o) {
        return this.getAge()-o.getAge();
    }
}
class test8{
    public static void main(String[] args){
        Student s1 = new Student(10, 160.5, "lili");
        Student s2 = new Student(14, 160, "lan");
        System.out.println(s1.compareTo(s2));
    }
}

注意:

  • 若想要将自定义的类型进行比较,一定要实现Comparable接口,重写里面的compareTo方法,里面的泛型传入你要比较的类型
  • compareTo方法的返回值为int类型

外部比较器

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

案例

public class Student{
    private int age;
    private double height;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public double getHeight() {
        return height;
    }
    public void setHeight(double height) {
        this.height = height;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student(int age, double height, String name) {
        this.age = age;
        this.height = height;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", height=" + height +
                ", name='" + name + '\'' +
                '}';
    }
}
class Contrast implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}
class test8{
    public static void main(String[] args){
        //比较名字
        Student s1 = new Student(10, 160.5, "lili");
        Student s2 = new Student(14, 160, "lili");
        Contrast contrast = new Contrast();
        System.out.println(contrast.compare(s1, s2));
    }
}

使用步骤

  • 自定义一个比较类,实现Comparator接口,重写里面的compare方法,将要比较的类型传入泛型
  • 通过调用该类compare方法的形式完成值的比较

TreeSet类

创建对象:TreeSet<Object> ts = new TreeSet<>([外部比较器]);

TreeSet的使用

内部比较器

public class Student implements Comparable<Student>{
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    //根据年龄进行比较
    @Override
    public int compareTo(Student o) {
        return this.getAge()-o.getAge();
    }
}
class Test{
    public static void main(String[] args) {
        Student s1 = new Student(13, "lili");
        Student s2 = new Student(8, "nana");
        Student s3 = new Student(5, "liu");
        Student s4 = new Student(13, "lan");
        TreeSet<Student> students = new TreeSet<>();
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        //根据年龄升序递增排序
        System.out.println(students);
    }
}

外部比较器

public class Student{
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
class Contrast implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getAge()-o2.getAge();
    }
}
class Test{
    public static void main(String[] args) {
        //利用外部比较器
        Contrast contrast = new Contrast();
        //一旦指定外部比较器就会按照外部比较器比较
        Student s1 = new Student(13, "lili");
        Student s2 = new Student(8, "nana");
        Student s3 = new Student(5, "liu");
        Student s4 = new Student(13, "lan");
        TreeSet<Student> students = new TreeSet<>(contrast);
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        //根据年龄升序排序
        System.out.println(students);
    }
}

注意:以上的添加元素方式就是给二叉树添加元素的方式(比较左子树与右子树)数值小的放左边,数值大的放右边 

TreeSet总结

  • 元素不能重复,并且无序(不会按照输入顺序进行输出)但是会按照升序方式进行遍历
  • TreeSet底层在调用空构造器时创建了TreeMap,通过TreeMap的key来控制TreeSet的特点
  • TreeSet底层是二叉树(数据结构中的逻辑结构),其用的遍历方式为中序遍历(左,根,右)刚刚好就是一个升序的遍历方式
  • 创建自定义类型的TreeSet集合那么该自定义类型必须有比较器,不然会报错
  • TreeSet创建对象时若构造方法若不传外部比较器,则默认用内部比较器比较,用内部比较器比较就要求要比较的类实现Comparable接口

Map接口

常用方法

增加

put(K key,V value):将指定键值放入map,返回重复了key而被移除的值,否则返回null

删除

clear():清空map所有键值,返回值为void

remove(Object key):将map中的指定键值删除,返回删除键对应的值

查看

entrySet():将map中的所有映射信息放入set集合,返回这个set集合

get(Object key):返回map中指定键所映射的值,若不包含映射关系则返回null

keySet():将map中的所有key放入set集合,返回这个set集合

values():将map中的所有value放入collection集合,返回这个collection集合

size():返回此map的长度

判断

containsKey(Object key):如果map中包含key则返回true,否则返回false

containsValue(Object value):如果map中包含value则返回true

equals(Object o):比较两个map内容是否相等,相等返回true,否则返回false

isEmpty():判断此map映射是否为空,为空返回true,否则返回false

        Map<String, Integer> map = new HashMap<>();
        map.put("lili", 1001);
        map.put("nana", 1005);
        System.out.println(map.put("lan", 1024));//null
        System.out.println(map.put("lan", 1024));//1024
        System.out.println(map.containsKey("nana"));
        System.out.println(map.containsValue(1024));
        System.out.println(map.remove("nana"));//1005
        System.out.println("map集合的长度:"+map.size());
        System.out.println("map集合是否为空:"+map.isEmpty());
        System.out.println(map);
        System.out.println(map.get("lili"));
        for (String s : map.keySet()) {
            System.out.println("key为:"+s);
        }
        for (Integer value : map.values()) {
            System.out.println("value为:"+value);
        }
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println("key为:"+entry.getKey()+"\tvalue为:"+entry.getValue());
        }
        map.clear();
        System.out.println(map);

Map集合的特点:键值对都是无序的,而且键不能重复,值可以重复,如果第二次put入相同的键,那么第二次put的键所对应的值将会把第一次的值覆盖

HashMap类

创建对象:HashMap<Object, Object> map = new HashMap<>([初始容量],[加载因子]);

注意:

  • HashMap初始容量为16,默认加载因子为0.75(存放的数据超过12就扩容)
  • 加载因子不是1的原因:减少哈希碰撞的发生
  • 传入的初始容量若小于16,直接初始容量变为2倍
  • HashMap的扩容按照2倍扩容
  • 如果链表的长度>8且数组长度>64时,链表会转为红黑树,当链表的长度<6时,红黑树会重新恢复成链表

HashMap原理

以下面代码为例

        HashMap<Integer, String> map = new HashMap<>();
        map.put(12, "lili");
        map.put(7, "lulu");
        map.put(9, "lala");
        map.put(16, "liu");
        map.put(6, "fafa");
        System.out.println(map.put(12, "lan"));
        System.out.println("集合中的长度"+map.size());//3
        System.out.println("集合"+map);

HashMap原理总结

  1. HashMap底层原理:采用数组+链表(哈希表)
  2. 存放数据时会通过元素的key调用hashCode函数来生成具体的key,再通过key经过哈希函数来计算出具体的放入数组的下标位置
  3. HashMap集合存放数据时会根据key来调用equals函数,看是否该元素的key在集合中已经存在,如果存在则直接替换掉该key原来的value值,返回原来被替换掉的value值,若不存在则不处理
  4. HashMap集合存放数据原理:将元素的key,value,通过key算出的哈希码,下一个元素的地址封装为对象(Entry对象),来向数组中进行存放
  5. 对于遇到不同key值的哈希冲突情况,jdk1.8的情况采用尾插法(就是后来的元素插到链表的后面)

HashMap与HashTable

HashTable与HashMap类似,不同点是HashTable是线程安全的,它几乎给所有的public方法都加上了synchronizatized关键字,还有一个不同点是HashTable的K,V都不能为null,HashMap就可以

LinkedHashMap类

  • LinkedHashMap为HashMap的子类
  • LinkedHashMap底层:哈希表+链表(多维护一张链表,对输入数据进行串联记录)
  • LinkedHashMap特点:键不能重复,有序(按照输入顺序进行输出)

TreeMap类

创建对象:TreeMap<Object, Object> map = new TreeMap<>([外部比较器]);

内部比较器使用

public class People implements Comparable<People>{
    private String name;
    private int age;
    public People(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 "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(People o) {
        //按照年龄排序
        return this.getAge()-o.getAge();
    }
}
class Test03{
    public static void main(String[] args) {
        People p1 = new People("lili", 23);
        People p2 = new People("lan", 25);
        People p3 = new People("nana", 23);
        TreeMap<People, Integer> map = new TreeMap<>();
        map.put(p1,1001);
        map.put(p2, 1003);
        map.put(p3, 1002);
        System.out.println(map);//23岁的只有1个,因为按照年龄排序
    }
}

外部比较器使用

public class People{
    private String name;
    private int age;
    public People(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 "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class Test03{
    public static void main(String[] args) {
        People p1 = new People("lili", 23);
        People p2 = new People("lan", 25);
        People p3 = new People("nana", 23);
        TreeMap<People, Integer> map = new TreeMap<>(new Comparator<People>() {
            @Override
            public int compare(People o1, People o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        map.put(p1,1001);
        map.put(p2, 1003);
        map.put(p3, 1002);
        System.out.println(map);
    }
}

TreeMap特点

  • 键不能重复,输出顺序按照升序或降序的方式
  • 底层的原理:二叉树,key遵循二叉树的特点
  • 放入集合的key的数据对应的类型内部一定要实现比较器(内部比较器或外部比较器二选一)
  • 如果创建对象时不指定外部比较器,那么就默认使用内部比较器
  • TreeMap存放数据的方式:将K,V,左子树地址,右子树地址,父节点地址,树节点颜色封装为一个对象(节点对象)进行存放
  • 自定义类型用比较器后,被比较的属性若重复,则新的value会替换掉老的value,但是key不变

Collections工具类

注意:

  • Collection类仅有一个空参构造器,构造器被私有化无法直接创建对象
  • 该类里面的所有属性以及方法都被static关键字修饰,我们可以通过类名直接调用

常用方法

addAll()(Collection c,T... elements):将后面的所有元素添加到集合c中,成功返回true,否则返回false

sort(Collection c):将c集合进行升序排序,返回值为void

reverse(Collection c):将c集合中的元素进行反转,返回值为void

Collections.binarySearch(Collection c, 集合中的元素):判断升序集合中元素在集合位置下的索引值并返回

copy(Collection c1, Collection c2):将c2集合中的元素替换到c1里面,如果c1长度长于c2,那么就会替换掉c1前面的数据,如果c1长度短于c2那么会抛出异常

fill(Collection c, 数据data):将c集合里的所有数据都替换为数据data

        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "lili","lala","lulu");//向集合中加入元素列表
        Collections.reverse(list);//反转集合
        Collections.sort(list);//将集合进行升序排序
        System.out.println(Collections.binarySearch(list, "lili"));//查找lili所在的集合下标
        System.out.println(list);
        ArrayList<String> list1 = new ArrayList<>();
        Collections.addAll(list1, "11","22");
        Collections.copy(list, list1);
        System.out.println(list);
        Collections.fill(list1, "aa");
        System.out.println(list1);