集合类超级无敌史无前例的超详细总结

48 篇文章 0 订阅

常用数据结构

链表:你可以想象成自行车链子 一个拴着一个(data数据域和指针next组成),单向链表指针永远指向下一个,最后一个指针是null。双向链表,则有头和尾,循环链表尾指向头。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
创建一个单链表的节点类

/**
 * 单向链表的节点
 */
public class Node {
    private Integer data; //节点中的数据
    private Node next; //储存的下一个节点(每个节点都保存了本节点的data和指向下一个节点的地址)

    public Node(Integer data) {
        this.data = data;
    }

    public Integer getData() {
        return data;
    }

    public void setData(Integer data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}

在这里插入图片描述

方法1:添加数据到链尾

public class SingleLinkedList {
   private Node head; // 创建单链表的头节点,因为链表是从头节点开始遍历,找到头节点才能顺着链子向后面遍历
    private int size;//表示单链表的长度

    public SingleLinkedList() {
    }

    public Node getHead() {
        return head;
    }

    public int getSize() {
        return size;
    }
    //向但链表的链尾添加数据
    public boolean add (Integer data){
        //创建一个你想要添加数据的节点,头空赋给头,不是空的赋给尾
        Node tmp = new Node(data);
        //判断头本身是否空(并不调用.next 不是判断下一个为空)
        if (head==null)
            head = tmp;
        //若头不是空
        else {
            //创建临时变量,把头赋给临时指针 因为需要head移动遍历
            Node t = head;
            //从头开始只要后面不为空,就一直往后走
            while (t.getNext()!=null){
                //只要后面的不为空,就把后面的值赋给t变量,新的t继续执行循环
                t = t.getNext();
            }
            //此时跳出循环,证明后一个为空,把你要赋值的节点给t.next(此时为空)
            t.setNext(tmp);
        }
        //执行完毕,链表长度+1
        size++;
        return true;
    }
}

public class SingleLinkedListTest {
    public static void main(String[] args) {
        //测试添加数据到队尾的add方法
        SingleLinkedList list = new SingleLinkedList();
        //自动装箱成Integer传入
        list.add(1);
        list.add(2);
        list.add(3);

    }
}

通过debug查看,发现每个next下都存在data和下一个next
在这里插入图片描述

2.指定位置添加数据
在这里插入图片描述
添加数据到下标位置,比如添加5到index2,为了不使原数据丢失,需要使包含data5的节点指向3的节点,然后断开2指向3的链子,使2指向5,这样指定下标的数据添加就完成了。


    /**
     * 添加数据到下标位置
     *
     * @param data  数据
     * @param index 下标
     * @return
     */
    public boolean add(Integer data, int index) {
        //创建一个你想要添加数据的节点
        Node tmp = new Node(data);
        //你要添加的下标大于等于数组长度
        if (index >= size) {
            throw new RuntimeException("越界异常!IndexOutOfBoundException,index:" + index + ",size" + size);
        }
        //你要在链表头插数据
        else if (index == 0) {
            //你的节点指向头
            tmp.setNext(head);
            //把你的节点赋值给头
            head = tmp;
        }
        //在头以后插数据
        else {
            //创建链表头的临时变量方便进行遍历
            Node t = head;
            for (int i = 0; i < index - 1; i++) {
                //每次循环都把下一个节点赋值给这个变量
                t = t.getNext();
            }
            //让你要存的节点指向下一个节点
            tmp.setNext(t.getNext());
            //让你存入节点的上一个节点指向你存入节点
            t.setNext(tmp);
        }
        size++;
        return true;
    }

    /**
     * 遍历链表的方法
     */
    public void print() {
        Node temp = head;
        while (temp != null) {
            System.out.println(temp.getData());
            temp = temp.getNext();
        }
    }

在这里插入图片描述
3.删除最后一个数据
在这里插入图片描述
当要删除最后一个节点时只要把前一个连接的链子断掉,就是删除了。

 /**
     * 删除最后一个节点:指向最后一个节点的链子断掉
     * @return
     */
    public Integer  remove(){
        //如果头节点为空
        if (head == null){
            throw new RuntimeException("链表为空,不能删除");
      //如果头节点的下一个为空,也就是只有头节点一个
        }if (head.getNext()==null){
            //获取头节点的data
            Integer data = head.getData();
            //给头节点赋空值
            head = null;
            //返回删除的数据
            return data;
        }
        //定义两个指针(tSlow在前tFast在后),满足条件就同时移动,
        // 直到fast为空,也就是说fast后面没有节点了
        Node tSlow =null;
        Node tFast =head;
        //只要快指针后面有节点就一直遍历
        while (tFast.getNext()!=null){
            //两个指针同时向后移动
            tSlow = tFast;
            tFast = tSlow.getNext();
        }
        //循环结束时,证明快指针后面为空,即快指针为最后一个节点
        //删除最后一个节点,只需要把前面和他相连的链子断了就行
        tSlow.setNext(null);
        //返回删除的数据(节点尾)
        return tFast.getData();
    }

4.删除指定下标的节点
在这里插入图片描述

/**
     * 根据下标删除节点:index前后连起来
     * @param index
     * @return
     */
    public Integer  remove(Integer index){
        if (index >= size){
            throw new RuntimeException("越界异常!IndexOutOfBoundException,index:" + index + ",size" + size);

        }if (index == 0){
            //获取头节点的data
            Integer data = head.getData();
            //删除后的头节点就是下一个节点了
            head = head.getNext();
            //返回删除的数据
            return data;
        }
        //要删除的下标不是0时,说明在头以后
        //定义两个指针(tSlow在前tFast在后),满足条件就同时移动,
        Node tSlow =null;
        Node tFast =head;
        //当循环到下标index时,最终tSlow指向你要删除的节点,tFast是index指向下个节点的指针
        for (int i = 0; i <index ; i++) {
            //两个指针向后移动
            tSlow = tFast;
            tFast =tFast.getNext();
        }
       //使指向index的指针指向index的下一个
        tSlow.setNext(tFast.getNext());
        //使index指向下个节点的指针为null,这样index的前一个和index的后一个就连起来了
        tFast.setNext(null);
        return tFast.getData();
    }

5.通过下标获取链表指定数据


    /**
     * 通过下标获取链表指定数据
     * @param index
     * @return
     */
    public Integer get(int index){
        if (index<0||index>=size){
            throw new RuntimeException("越界异常!IndexOutOfBoundException,index:" + index + ",size" + size);
        }
            Node temp = head;
            for (int i = 0; i <index ; i++) {
               temp =temp.getNext();
        }
            return temp.getData();
    }

二叉树:
二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥 有的子树数)。
在这里插入图片描述
在这里插入图片描述
1号位于根节点,其余叫做子节点,每个节点相当于被分成三部分,一份存放自身的数据,另两份存放左右两个节点的地址。

创建二叉树节点

/**
 * 二叉树的节点
 */
public class TreeNode {
    //数据
    private Integer data;
    //左节点 默认null
    private  TreeNode left;
    //右节点 默认null
     private TreeNode right;

    public TreeNode(Integer data) {
        this.data = data;
    }

    public Integer getData() {
        return data;
    }

    public void setData(Integer data) {
        this.data = data;
    }

    public TreeNode getLeft() {
        return left;
    }

    public void setLeft(TreeNode left) {
        this.left = left;
    }

    public TreeNode getRight() {
        return right;
    }

    public void setRight(TreeNode right) {
        this.right = right;
    }
}

创建一颗树

  public static void main(String[] args) {
        //创建7个节点
        TreeNode root = new TreeNode(1);
        TreeNode node2 = new TreeNode(2);
        TreeNode node3 = new TreeNode(3);
        TreeNode node4 = new TreeNode(4);
        TreeNode node5 = new TreeNode(5);
        TreeNode node6 = new TreeNode(6);
        TreeNode node7 = new TreeNode(7);
        //这些节点构建为一棵树
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);
        node3.setRight(node7);

    }

在这里插入图片描述
广度优先遍历:按层遍历

 /**
     * 广度优先遍历:按层打印,先父后子,取出父的时候子进行排队,子取出,子的子再排队以此类推
     * @param root
     */
    public static void printByLayer(TreeNode root){
        //根节点为空直接return
        if (root==null){
            return;
        }
        //创建一个队列存储节点  队列特点:先进先出
        Queue<TreeNode> queue = new LinkedList<>();
        //把根节点存入队列
        queue.add(root);
        //只要队列里面有东西,就一直循环
        while (!queue.isEmpty()){
            //从队列中取出一个节点赋值给临时变量
            TreeNode temp = queue.poll();//弹出数据
            //打印这个数据
            System.out.println(temp.getData());
            //开始管理自己的子节点
            if (temp.getLeft()!=null)
                queue.add(temp.getLeft());
            if (temp.getRight()!=null)
                queue.add(temp.getRight());

        }

    }

在这里插入图片描述
深度优先遍历
在这里插入图片描述

 /**
     * 前序遍历:根左右
     *
     * @param root
     */
    public static void pre_print(TreeNode root) {
        if (root!=null){
            //根
            System.out.println(root.getData());
            //左
            if (root.getLeft()!=null)
                pre_print(root.getLeft());
            //右
            if (root.getRight()!=null)
                pre_print(root.getRight());
        }
    }

    /**
     * 中序遍历:左根右
     *
     * @param root
     */
    public static void mid_print(TreeNode root) {
        if (root!=null){

            //左
            if (root.getLeft()!=null)
                mid_print(root.getLeft());
            //根
            System.out.println(root.getData());
            //右
            if (root.getRight()!=null)
                mid_print(root.getRight());
        }
    }

    /**
     * 后序遍历:左右根
     *
     * @param root
     */
    public static void last_print(TreeNode root) {
        if (root!=null){

            //左
            if (root.getLeft()!=null)
                last_print(root.getLeft());
            //右
            if (root.getRight()!=null)
               last_print(root.getRight());
            //根
            System.out.println(root.getData());
        }
    }

二叉排序树:
在这里插入图片描述

/**
 * 二叉排序树
 */
public class BinarySortTree {
    //定义根节点
    private TreeNode root;

    /**
     * 二叉排序树的添加 :根据根节点比较小于根节点放左边,大于根节点放右边
     * 注意点一:如果插入的值,树中已经存在,则不会继续插入
     * 注意点二:遍历要采用中序遍历(左根右->小中大)
     * @param data
     * @return
     */
    public boolean add(Integer data){

        if (root == null){
            this.root = new TreeNode(data);
        }else {
            //定义当前节点
            TreeNode current = root;
            //定义相对的根节点
            TreeNode parentNode = null;
            //只要当前节点不为空就一直循环
            while (current!=null){
                //把当前节点作为根节点
                parentNode  = current;
                //传入的数据小于当前节点
                if (data < current.getData()){
                    //把当前节点的左侧当作当前节点
                    current = current.getLeft();
                    if (current == null){
                        parentNode.setLeft(new TreeNode(data));
                        return true;
                    }
                }
                //传入的数据大于当前节点
               else {
                    //把当前节点的右侧当作当前节点
                    current = current.getRight();
                    if (current == null){
                        parentNode.setRight(new TreeNode(data));
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public TreeNode getRoot() {
        return root;
    }

    public void setRoot(TreeNode root) {
        this.root = root;
    }
}

测试及结果(采用中序遍历)
在这里插入图片描述
查找指定数据(get)

 /**
     * 查询树中有没有对应的值
     * @param key
     * @return
     */
    public TreeNode get(Integer key){
        TreeNode temp = root;
        while (temp!=null){
            if (temp.getData()>key){
                temp = temp.getLeft();
            }else if(temp.getData()<key){
                temp = temp.getRight();
            }else {
                return temp;
            }
        }
        return null;
    }

在这里插入图片描述

在这里插入图片描述
若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树
在这里插入图片描述
栈:
在这里插入图片描述
队列:
在这里插入图片描述
数组:
在这里插入图片描述

集合和数组既然都是容器,它们有啥区别呢?

数组的长度是固定的。集合的长度是可变的

数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类
型可以不一致。在开发中一般当对象多的时候,使用集合进行存储

类集设置的目的

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。
在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。
类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。
所有的类集操作的接口或类都在 java.util 包中。
Java 类集结构图:
在这里插入图片描述

Collection 接口

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在 java.util 包中。C ollection一共定义了15个方法(比如迭代器,size,add),但我们实际开发中并不会直接去操作Collection,而是去操作他的子接口(List和Set)。

常用方法:

。 public boolean add(E e) : 把给定的对象添加到当前集合中

。 public void clear() :清空集合中所有的元素

。 public boolean remove(E e) : 把给定的对象在当前集合中删除

。 public boolean contains(E e) : 判断当前集合中是否包含给定的对象

。 public boolean isEmpty() : 判断当前集合是否为空

。 public int size() : 返回集合中元素的个数

。 public Object[] toArray() : 把集合中的元素,存储到数组中
。没有get方法,ArrayList和Vector有

Iterator

迭代器,区别于以前的遍历是系统迭代数据结构的最优实现,每个类集都会自带一个迭代器。

List接口

List也是保存单值的借口,同时对于保存的数据规定是有序,可重复的。
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充(比如拥有自己的迭代器ListIterator)。我们平时使用List接口的时候则是针对其实现类(ArrayList,Vector,LinkedList)进行操作。

ArrayList

传统数组虽然取值比较快速,但是对于增加或者修改尤其缓慢。数组一旦创建则不可改变,如果我们添加的值超过了数组的界限,则我们需要手动扩容(非常麻烦,且耗费内存)。对于删除输出来说,删除一个数据需要后面的所有数据往前位移,如果是一百万个数据呢???
使用ArrayList则完美的解决了这个问题,相比于数组,ArrayList是可自动扩容的数组,我们无须再手动扩容。底层数据结构依然是数组结构array,所以查询速度快,增删改慢。

通过源码分析其实现,ArrayList可以通过构造方法在初始化的时候指定底层数组的大小。
通过无参构造方法的方式ArrayList()初始化,则赋值底层数Object[] elementData为一个默认空数组Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}所以数组容量为0,只有真正对数据进行添加add时,才分配默认DEFAULT_CAPACITY = 10的初始容量。

大家可以分别看下他的无参构造器和有参构造器,无参就是默认大小,有参会判断参数。
在这里插入图片描述

ArrayList 会自动判断传入长度,当数组长度不够的时候就会自动扩容
jdk1.8以后大概是这个样子
在这里插入图片描述

原数组容量右移一位,此操作在二进制的基础上位移动。举个例子十进制右移一位(10就 变成了1,相当于除10),二进制(正数)则是最右位去掉,最高位补0,则相当于除以二。
我们看这个公式就是 新长度 = 旧长度+0.5旧长度。 所以 ArraysList每次扩容都是之前的长度都是之前的1.5倍。

同时ArraysList是线程不安全的,Vector 是线程安全的。
ArrayList和Vector相同点与区别:

同:

1 ArrayList和Vector都是继承了相同的父类和实现了相同的接口 2 底层都是数组(Object[])实现的

3 初始默认长度都为10。

区别:

1 同步性:

Vector中的public方法多数添加了synchronized关键字、以确保方法同步、也即是Vector线程安全、ArrayList线程不安全。

2 扩容:

ArrayList以1.5倍的方式在扩容、Vector
当扩容容量增量大于0时、新数组长度为原数组长度+扩容容量增量、否则新数组长度为原数组长度的2倍

3性能:

在性能方便通常情况下ArrayList的性能更好、而Vector存在synchronized
的锁等待情况、需要等待释放锁这个过程、所以性能相对较差。

4 输出:

ArrayList支持支持 Iterator、ListIterator 输出,Vector除了支持
Iterator、ListIterator外,还有Enumeration输出

ArrayList常用方法

List接口常用方法:

1add(Object element): 向列表的尾部添加指定的元素。

2size(): 返回列表中的元素个数。

3get(int index): 返回列表中指定位置的元素,index从0开始。

4add(int index, Object element): 在列表的指定位置插入指定元素。

5set(int i, Object element): 将索引i位置元素替换为元素element并返回被替换的元素。

6clear(): 从列表中移除所有元素。

7isEmpty(): 判断列表是否包含元素,不包含元素则返回 true,否则返回false8contains(Object o): 如果列表包含指定的元素,则返回 true9remove(int index): 移除列表中指定位置的元素,并返回被删元素。

10remove(Object o): 移除集合中第一次出现的指定元素,移除成功返回true,否则返回false11iterator(): 返回按适当顺序在列表的元素上进行迭代的迭代器。


ArrayList一样可以使用List的所有方法

Vector

线程安全,因为公开的方法都加了锁,同时导致了性能不好。和ArraysList一样使用了数组的结构,查找快,增删慢。ArrayList有的方法他都有。

LinkedList

使用的是双向链表,增删快,查找慢。
存储的结构是链表结构 List里面的方法多有 同时还有自己特有的

。public void addFirst(E e) :将指定元素插入此列表的开头

。 public void addLast(E e) :将指定元素添加到此列表的结尾

。 public E getFirst() :返回此列表的第一个元素

。 public E getLast() :返回此列表的最后一个元素

。 public E removeFirst() :移除并返回此列表的第一个元素

。 public E removeLast() :移除并返回此列表的最后一个元素

。 public E pop() :从此列表所表示的堆栈处弹出一个元素

。 public void push(E e) :将元素推入此列表所表示的堆栈

。 public boolean isEmpty() :如果列表不包含元素,则返回true。

方法详见:https://blog.csdn.net/vjrmlio/article/details/7950887#

Iterator接口

用来遍历Collcection集合下的所有集合 List Set…

public E next() :返回迭代的下一个元素 同时指针下移。

public E previous() :返回迭代的上一个元素 同时指针上移。

public boolean hasNext() :如果仍有元素可以迭代,则返回 true

增强for

for(数据类型 变量名 : 集合 或 数组名){}

失败:遍历的时候,改变了集合,导致遍历失败
快速失败:创建迭代器后的任何时间,除了自己的remove以外修改集合,迭代器将抛出ConcuurentModificationException.
安全失败:把集合复制了一份,我们遍历的是复制的,所以怎么操作无所谓,通常使用的是安全失败。

使用迭代器遍历集合的内容:

1.通过调用集合的Iterator()方法,获取指向集合开头的迭代器。

2.建立一个hasNext()方法调用循环。只要hasNext()方法返回true,就继续迭代。

3.在循环中,通过调用next()方法获取每个元素。

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


public class IteratorDemo {
    public static void main(String[] args) {
        //create an Array list
        ArrayList<String> arrayList = new ArrayList<String>();
 
        arrayList.add("q");
        arrayList.add("e");
        arrayList.add("fg");
        arrayList.add("iu");
        arrayList.add("ug");
 
 
        System.out.println("Original contents of arraylist: ");
        //创建ArrayList的迭代器
        Iterator<String> iterator = arrayList.iterator();
        //iterator.hasNext()判断指针指向的下一个是否为空,返回值是boolean
        while (iterator.hasNext()){
        //iterator.next()获取元素并且指针指向后移
            String element = iterator.next();
            System.out.print(element +" ");
        }
        System.out.println();
 
        //Modify objects being iterated
        ListIterator<String> lit = arrayList.listIterator();
        while (lit.hasNext()){
            String element = lit.next();
            lit.set(element + "+");
        }
        System.out.println("Modified contents of arraylist: ");
        iterator = arrayList.iterator();
        while (iterator.hasNext()){
            String element = iterator.next();
            System.out.print(element + " ");
        }
        System.out.println();
 
        //Now,display the list backwards.
        System.out.println("Modified list backwards: ");
        while (lit.hasPrevious()){
            String element = lit.previous();
            System.out.print(element + " ");
        }
        System.out.println();
 
    }
}


ListIterator

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

介绍一下新来的几个方法:

void hasPrevious() 判断游标前面是否有元素;
Object previous() 返回游标前面的元素,同时游标前移一位。游标前没有元素就报 java.util.NoSuchElementException的错,所以使用前最好判断一下;
int nextIndex() 返回游标后边元素的索引位置,初始为 0 ;遍历 N 个元素结束时为 N;
int previousIndex() 返回游标前面元素的位置,初始时为 -1,同时报
java.util.NoSuchElementException 错;
void add(E) 在游标 前面 插入一个元素 注意是前面
void set(E) 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。 注意,当没有迭代,也就是没有调用 next() 或者 previous() 直接调用 set 时会报错
void remove() 删除迭代器最后一次操作的元素,注意事项和
set 一样。

forEach

forEach:增强for循环,最早出现在C#中
用于迭代数组 或 集合 (Collection下的集合才行list和set)(自动寻在最优解)
for(数据类型 :变量名 :集合或者数组名){
// 变量一只在循环中改变
System.out.println(变量名)
}

Set(接口)

继承自Collection,方法几乎一致,并没有太多的改变。和Collection一样没有get方法,只有用toArray变成数组或者iterator迭代器找数据,这是为什么呢?因为Set的实现类不包含重复单值的集合,及不满足e1.equals(e2) 且 null 也最多就一个。
如果使用可变对象作为set元素则需要非常小心,比如传入一个Person,里面有属性name age 结果被改变了 ,位置也就改变。

HashSet

没有实现过多自己的方法,基本还是用Colletion的方法,没有get,只能toArray变成数组操作或者迭代器操作。内部是散列存放,基于hashmap存储。
换个方式理解,我们编写一个软件,存储数据,长度不确定,用数组不合适,选择集合,不增删只查找,我们就可以用ArrayList,我们使用,系统已经提供了动态扩容结构。 设计哈希表,hashset是单值, 之前map已经是双值存储的hash表了 ,系统怎么设计呢, 就利用了这个hash表
put 数据 一定要是两个 一个是我们传入的 一个是写死的 在这里插入图片描述
add方法返回的是boolean类型,基于set无序的不可重复的特点,因为存在所以添加失败为false,因为不可重复所以输出以下结果。至于如何实现无序和不可重复的呢?详见后文hashmap
在这里插入图片描述

TreeSet和Comparable

TreeSet内部也是用treemap实现的,Hashset无序,TreeSet有序(自然顺序,不是根据你输入的顺序,系统根据Ascall码排序)
注意:如果想根据你自定义的顺序进行排序输出,要在类型的类中实现Compareble<类型>接口,否则直接add会抛异常,如图。
在这里插入图片描述

在这里插入图片描述
类型转换异常,因为系统也不知道怎么给你排,你需要实现接口重写里面的compareTo方法。

//实现Comparable,写要比较的类型
static class Person implements Comparable<Person>{
        private String name;
        private int age;

    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) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                name.equals(person.name);
    }

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


    @Override
    public int compareTo(Person o) {
    //this 与 泛型 传入的o比较
    //返回的数据:负数:this小/零 一样/正数 this 大
    //里边写重写的方法
        return 0;
    }
 
//   Person p1 = new Person("张三",18);
//   Person p2 = new Person("李四",19);
//   data.add(p1);
//   data.add(p2);
// p1存的时候不排序,P2存的时候,就相当于p1.comPareTo(p2)
//根据返回的数据决定谁大谁小存在不同的位置
     @Override
    public int compareTo(Person o) {
       if(this.age>o.age){
       return 1;
       }else if (this.age == o.age){
       return 0;
  }
       return -1;
    
    }


}

自定义排序规则后的输出(根据自身业务需要)
在这里插入图片描述
但是如果我们添加了一个添加了一个18岁的王二麻子会怎么样?

在这里插入图片描述
在这里插入图片描述
我们发现王二麻子并没有输出,因为set是不能重复的。

Compartor接口

https://blog.csdn.net/lx_nhs/article/details/78871295

Map

注意:List(单值),Set(单值),Map(双值) 并不在同一等级。Map和Collection是同一个级别,只不过我们很少用Collection。
Map集合存储的是一个个的 键值对 数据。

左边是像list set单值存储结构,右边是map双值结构。
左边通过迭代器方式或下标方式获取元素,右边则根据key获取value。

在这里插入图片描述
之前说hashset内部是hashmap实现的,treeset是用treemap实现的,这是为什么?

相当于利用了map的key(不可重复性)来存储数据。
在这里插入图片描述
右边的已经被写死,左边利用map的k来存储数据。

map不是地图的意思(谷歌翻译有误),是映射的意思(mapping的缩写),每个键只能映射一个值。因为键和值都不是有序的,根据我们定义的,所以我们无法通过遍历得到他们的值。我们可以把key都保存在keyset里面,再遍历keyset()得到所有的key,然后再调用get方法传入key取出value; put方法存值
在这里插入图片描述
在这里插入图片描述

如图,为什么put(k,v)的时候需要返回值呢?
因为map不允许存重复的值所以当我们put一个k一样的值但是v不一样的值的时候,系统会用新值替换旧的值,就会把旧值返回回来。如果没有替换,只是单纯的添加,那就会返回null。

remove为什么也返回v?
删除成功返回的就是删除的值,删除失败就返回null。我们有时候也用remove取出数据,如果需要取出并删除可以使用remove

size() 获取到键值对的数量

哈希表

一篇不错的文章

数组是我们平时常见的并且经常使用的一种数据结构,那么它具有什么优点呢?我们都知道,在我们知道数组中某元素的下标值时,我们可以通过下标值的方式获取到数组中对应的元素,这种获取元素的速度是非常快的。

但是呢,数组也是有一定的缺点的,如果我们不知道某个元素的下标值,而只是知道该元素在数组中,这时我们想要获取该元素就只能对数组进行线性查找,即从头开始遍历,这样的效率是非常低的,如果一个长度为10000的数组,我们需要的元素正好在第10000个,那么我们就要对数组遍历10000次,显然这是不合理的。

所以,为了解决上述数组的不足之处,引入了哈希表的概念,哈希表在很多语言的底层用到的非常的多

哈希表的优缺点

在刚才哈希表的描述中,大家一定能看出哈希表的优缺点了,这里我来给大家总结一下吧~

(1)优点

首先是哈希表的优点:

无论数据有多少,处理起来都特别的快
能够快速地进行 插入修改元素 、删除元素 、查找元素 等操作
代码简单(其实只需要把哈希函数写好,之后的代码就很简单了)
(2)缺点

然后再来讲讲哈希表的缺点:
哈希表中的数据是没有顺序的
数据不允许重复
(3)冲突

前面提到了冲突,其含义就是在哈希化以后有几个元素的下标值相同,这就叫做 冲突。 那当两个元素的下标值冲突时,是后一个元素是不是要替换掉前一个元素呢?当然不是!

那么如何解决冲突这个现象呢?一般是有两种方法,即拉链法(链地址法)开放地址法
拉链法:如果存的都在同一个位置就使用链表,一个绑着一个
开放地址法:如果存的过多就使用红黑树

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
原文详见

实例:
在这里插入图片描述
哈希表使用的是对象数组+链表,或者对象数组+红黑树
比如Hashmap默认初始长度为16的对象数组+链表结构,下标是0-15,当我们存入一个数据时,先通过hashCode()计算其hash值(返回一个int),然后对其长度(桶数量)(此时是16求余%得到一个0-15下标的数)根据该数找到对应的哈希桶(位置)。那么如果计算出来比如17%16=1 33%16=1,两个数值一样怎么办?哈希冲突:有两种解决办法,这里我们只用开放地址法。
当第二次计算33%16得出1的时候,查看2下标的位置有没有,如果有就继续以此类推。。。

Hash表、Hash函数和HashCode
jdk1.8以后针对哈希表进行了优化,当哈希桶达到8时候,转换为红黑二叉树进行存储。当哈希桶数量减少到6的时候,从红黑二叉树转换为链表。
在这里插入图片描述
假设我们存了一万个数据,那么这16个桶(无法确定初始值,就设置16),每一个桶都有大几百个数据,这样性能肯定会变的很慢。
散列因子:0.75 当16个桶中有百分之七十五存上数据了就对桶扩容,桶就会更多,默认长度是桶的两倍
那么下次取余就是32了 比如:322%32

HashMap源码分析
在这里插入图片描述
散列因子(默认0.75,也可以通过有参构造修改,官方最推荐0.75)
在这里插入图片描述
默认容量16,1左移四位
在这里插入图片描述
put方法
在这里插入图片描述

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

在这里插入图片描述

冲突:链表或红黑树解决

HashMap/Hashtable/ConcurrentHashMap

HashMap 线程不安全的,效率高
在这里插入图片描述

Hashtable 线程安全的,效率低
在这里插入图片描述
ConcurrentHashMap
采用分段锁机制,保证线程线程安全,效率又比较高
在这里插入图片描述
在链表内分段排队,举例(0号位置A在执行,B计算出来去1号位置执行,遇到同一个位置的在排队,没有就去对应位置执行)

散列表的几点说明

散列因子(默认0.75):太小耗费空间,但是效率高(比如0.1当有十分之一个桶装了就扩容)
太大不耗费空间,但效率太低(比如0.9但有十分之九装了才扩容,很可能一个桶已经上千条数据了)
容量(桶的个数):默认16 比如要存10000 数据,产生了大量散列 ,初始容量一定要给的合理,本身hash表就是存取效率很高,不要因为人为操作失误导致效率变慢。
存储在hashmap的key如果是自定义类型就不要修改值

使用map,尤其是hashmap 一定要支持equals 和hashcode

在这里插入图片描述
当我们在get方法前修改key值时,由于get方法再调用的时候会计算hash值导致计算出来的hash值和之前不一样,就去了错误的桶里面找,当然找不到,结果为null。
在这里插入图片描述
在这里插入图片描述
此时你想通过原来的key找到value发现,这是为什么?明明我的key一样啊,却找不到value。原来hashmap在get()时不仅会调用hashcode计算hash值(key),还会调用equals判断。
在这里插入图片描述

在这里插入图片描述
所以,当我们在使用map,尤其是hashmap的时候,当作为哈希表的key存储时key就不要去改变,如果需要改变的就不要放在key上

Jdk9集合类的新特性

只对List Set Map 使用,子类不行
只能创建固定长度的集合,不可改变,不可以add,remove,set不能修改

      //Set
        Set<String> set = Set.of("锄禾日当午","汗滴禾下土","谁知盘中餐","粒粒皆辛苦");
        for (String s : set) {
            System.out.println(s);
        }
        //List

        List<Integer> list = List.of(1,2,3,4);
        for (Integer integer : list) {
            System.out.println(integer);
        }
        //Map
        Map<String,String> map = Map.of("1","锄禾日当午","2","汗滴禾下土","3","谁知盘中餐","4","粒粒皆辛苦");
        Set<String> keySet = map.keySet();
        for (String key: keySet) {
            System.out.println(key+"->"+map.get(key));
        }

在这里插入图片描述

最后再来个全家福
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值