04.java面向对象-集合

04.java面向对象-集合

01. 集合的理解和好处

​ 一般情况下我们保存多个数据使用的是数组, 但是数组有不足的地方, 会影响一些方面
分析一下。

1. 数组

  1. 长度开始时必须指定,而且一旦指定,不能更改
  2. 保存的必须为同一类型的元素
  3. 使用数组进行增加元素的示意代码比较麻烦

2. 写出arr数组扩容示意代码

public class ArrayResizeExample {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int newElement = 6;

        // 扩容
        int[] newArr = new int[arr.length + 1];

        // 将原数组中的元素逐个复制到新数组中
        for (int i = 0; i < arr.length; i++) {
            newArr[i] = arr[i];
        }

        // 添加新元素到新数组中
        newArr[arr.length] = newElement;

        // 更新原数组引用
        arr = newArr;

        // 打印结果
        System.out.println("Array after resize: " + Arrays.toString(arr));
    }
}

总体来说不是特别的困难,但是当你要处理数据变大时就会非常复杂

3. 集合

  1. 可以动态保存任意多个对象,使用比较方便
  2. 提供一系列方便的操作对象的方法:add、remove、set、get
  3. 使用集合添加,删除新元素的示意代码-简洁
  4. 可以存储不同类型的元素
public class Collection_ {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        //1. 集合主要分为两组(单列集合,双列集合)
        //2. Collection 接口有两个重要的子接口 List Set, 他们实现子类都是单例集合
        //3. Map 接口实现子类 是双列集合
//        Collection
//        Map
        ArrayList arrayList = new ArrayList();
        arrayList.add("jack");
        arrayList.add("tom");

        HashMap hashMap = new HashMap();
        hashMap.put("like","son");
        hashMap.put("love","husband");
    }
}

02. Collection接口和常用方法

1. Collection接口实现类的特点

public interface Collection<E> extends Iterable

  1. collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类,可以存放重复元素,有些不可以
  3. 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
  4. Collection接口没有直接实现子类,是通过它的子接口Set和List来实现的

2. Collection接口常用方法,以实现子类ArrayList来演示

  1. add:添加单个元素
  2. remove:删除指定元素
  3. contains:查找元素是否存在
  4. size:获取元素个数
  5. isEmpty:判断是否为空
  6. clear:清空
  7. addAll:添加多个元素
  8. containAll:查找多个元素是否存在
  9. removeAll:删除多个元素
  10. 说明:以ArrayList实现类来演示
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CollectionMethod {
    public static void main(String[] args) {
        List list = new ArrayList();

//        1. add:添加单个元素
        list.add("jack");
        list.add(10);
        list.add(true);
        System.out.println("list = " + list);
//        2. remove:删除指定元素(指定索引或者元素)
//        list.remove(0);//删除第一个元素
        list.remove(true);//指定删除某个元素
        System.out.println("list=" + list);
//        3. contains:查找元素是否存在
        System.out.println(list.contains("jack"));
//        4. size:获取元素个数
        System.out.println(list.size());
//        5. isEmpty:判断是否为空
        System.out.println(list.isEmpty());
//        6. clear:清空
//        list.clear();

//        7. addAll:添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("list=" + list);
//        8. containAll:查找多个元素是否存在
        System.out.println(list.containsAll(list2));
//        9. removeAll:删除多个元素
        list.add("西游记");
        list.removeAll(list2);
        System.out.println("list=" + list);
//        10. 说明:以ArrayList实现类来演示
    }
}

3. Collection接口遍历元素方式1-使用Iterator(迭代器)

1. 基本介绍
  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
  2. 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
  3. Iterator仅用于遍历集合,Iterator本身并不存放对象
2. 迭代器的执行原理

Iterator iterator = coll.iterator();//得到一个集合的迭代器
//hasNext(); 判断是否有下一个元素
while(itetator.hasNext()) {
//next(); 1.指针下移 2.将下移以后集合位置上的元素返回 System.out.println(iterator.next());
}
提示:
​ 在调用Iterator.next方法之前必须调用Iterator.hasNext()进行检测. 若不调用, 且下一条记录无效,直接调用iterator.next()会抛出NoSuchException异常

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

/**
 * Iterator迭代器
 **/
public class CollectionIterator {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));
        col.add(new Book("红楼梦", "曹雪芹", 34.6));
        System.out.println("col" + col);
        //希望能够遍历col集合
        //1. 先得到 col对应的 迭代器
        Iterator iterator = col.iterator();
        //2. 使用while循环
//        while (iterator.hasNext()) {//判断是否还有数据
//            Object obj = iterator.next();
//            System.out.println("obj" + obj);
//        }

        //快捷键,快速生成while - 键盘敲itit
        //显示所有快捷键的快捷键 ctrl + j
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj" +obj);
        }
        //3. 当退出while循环,这是Iterator迭代器,指向最后的元素,不能重新遍历
        //4. 如果希望再次遍历,需要重置迭代器
        iterator = col.iterator();
        System.out.println("==========第二次=========");
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj =" + obj);
        }
    }

}
class Book {
    private String name;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

4. Collection接口遍历对象方式2-for循环增强

1. 基本介绍

​ 增强for虚化,可以替代Iterator迭代器,特点:增强for就是简化版Iterator,本质一样。只能用于遍历结合或数组。

2. 基本语法

for(元素类型 元素名:集合名或数组名) {
访问元素
}

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

/**
 * for循环增强
 **/
public class CollectionFor {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));
        col.add(new Book("红楼梦", "曹雪芹", 34.6));

        //1, 使用增强form, 在Collection集合
        //2. 增强for,底层仍然是迭代器
        //3. 可以理解为简化版本的 迭代器遍历
        //4. 快捷方式 I
        for (Object book:col) {
            System.out.println("book=" + book);
        }
//        //增强for,也可以在数组使用
//        int[] arr = {1, 3, 5, 6, 7};
//        for (int i:arr) {
//            System.out.print(i + "\t");
//        }
    }
}

03. List接口和常用方法

1. List接口基本介绍

​ List接口Collection接口的子接口

  1. List结合类中元素有序(即添加顺序和取出顺序一致), 且可重复
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引
  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
  4. JDK API中List接口实现类常用的有:
    1. ArrayList
    2. vector
    3. LinkedList
import java.util.ArrayList;
import java.util.List;

/**
 * List接口
 **/
public class List_ {

    public static void main(String[] args) {
        //1. List结合类中元素有序(即添加顺序和取出顺序一致), 且可重复
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("mary");
        list.add("yzj");
        list.add("tom");//可重复
        System.out.println(list);

        //2.List集合中的每个元素都有其对应的顺序索引,即支持索引
        //索引是从零开始的
        System.out.println(list.get(3));//yzj
    }
}

2. List接口常用方法

直接使用案例进行演示

import java.util.ArrayList;
import java.util.List;
/**
 * List常用方法
 **/
public class ListMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("张三丰");
        list.add("贾宝玉");

        //1. void add(int index, Object ele):在 index 位置插入 ele 元素
        //在 index = 1 的位置插入一个对象
        list.add(1, "yzj");
        System.out.println("list=" + list);

        //2. boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
        List list2 = new ArrayList();
        list2.add("jack");
        list2.add("tom");
        list.addAll(1, list2);
        System.out.println("list=" + list);

        //3. Object get(int index):获取指定 index 位置的元素
        //说过

        //4. int indexOf(Object obj):返回 obj 在集合中首次出现的位置
        System.out.println(list.indexOf("tom"));//2

        //5.int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
        list.add("yzj");
        System.out.println("list=" + list);
        System.out.println(list.lastIndexOf("yzj"));

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

        //7. Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换. list.set(1, "玛丽");
        System.out.println("list=" + list);

        //8. List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
        // 注意返回的子集合 fromIndex <= subList < toIndex
        List returnlist = list.subList(0, 2);
        System.out.println("returnlist=" + returnlist);
    }
}

3. List的三种遍历方式

  1. 方式一:使用Iterator
    Iterator iter = col.iterator();
    while(iter.hasNext()) {
        Object obj = iter.next();
    }
    
  2. 方式二:使用增强for循环
    for(Object o : col) {
        
    }
    
  3. 方式三:使用普通for循环
    for(int i = 0; i < list.size(); i++) {
        Object obj = list.get(i);
        System.out.println(obj);
    }
    

04. ArrayList底层结构和源码分析

1. ArrayList的注意事项

  1. permits all elements, including null, ArrayList可以加入null,并且多个
  2. ArrayList 是由数组来实现数据存储的
  3. ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码,在多线程情况下,不建议使用ArrayList
import java.util.ArrayList;

/**
 * ArrayList注意事项
 **/
public class ArrayList_ {
    public static void main(String[] args) {
        //ArrayList 是线程不安全的,可以看源码 没有 Synchronized
        /*
            public boolean add(E e) {
                modCount++;
                add(e, elementData, size);
                return true;
        }
        * */
        ArrayList arrayList = new ArrayList();
        arrayList.add(null);
        arrayList.add("jack");
        arrayList.add(null);
        System.out.println(arrayList);
    }
}

2. ArrayList底层操作机制和源码分析(重点、难点)

  1. ArrayList中维护了一个Object类型的数组elementData.[看源码]
    transient Object[] elementData;//transient 短暂的,表示该属性不会被序列化
  2. 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0(jdk7是10)
  3. 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置
  4. 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍
  5. 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity
  6. 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容为elementData为1.5倍
  7. 里面涉及到了诸多源码的分析,建议大家去看看ArrayList的扩容和缩容的过程

05. Vector底层结构和源码剖析

1. Vector类的基本介绍

  1. Vector类的定义说明
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable,Serializable
  1. Vector底层也是一个对象数组,protected Object[] elementData;
  2. Vector是线程同步的,即线程安全,Vector类的操作方法有synchronized
public synchronized E get(int index) {
    if(index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    return elementData(index);
}
  1. 在开发中,需要线程同步安全时,考虑使用Vector

2. Vector和ArrayList的比较

ArrayListVector
底层结构可变数组可变数组
版本jdk1.2jdk1.0
线程安全(同步效率)不安全,效率高安全,效率不高
扩容倍数如果有参构造1.5倍
如果是无参
1.第一次10
2.从第二次1.5扩
如果无参,默认10,满后,就按2倍扩容
如果指定大小,则每次直接按照两倍扩

06. LinkedList底层结构

1. LinkedList的全面说明

  1. LinkedList实现了双向链表和双端队列的特点
  2. 可以添加任意元素(元素可以重复),包括null
  3. 线程不安全,没有实现同步

2. LinkedList的底层操作机制

  1. LinkedList底层维护了一个双向链表
  2. LinkedList中维护了两个属性first和last分别指向 首节点和尾节点
  3. 么个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
  4. 所以LinkedList的元素不是通过数组完成的,相对来说效率较高。
模拟一个简单的双向链表
public class LinkedList_ {
    public static void main(String[] args) {
        //模拟一个简单的双向链表
        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node yzj = new Node("yzj");
        //连接三个结点,形成双向链表
        //jack -> tom -> yzj
        jack.next = tom;
        tom.next = yzj;
        //yzj -> tom -> jack
        yzj.pre = tom;
        tom.pre = jack;
        Node first = jack;//让first引用指向jack,就是双向链表的头结点
        Node last = yzj;//让last引用指向yzj,就是双向链表的尾结点

        //演示 从头到尾遍历
        System.out.println("从头到尾");
        while(true) {
            if (first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }

        //演示 从尾到头
        System.out.println("从尾到头");
        while(true) {
            if (last == null) {
                break;
            }
            //输出first 信息
            System.out.println(last);
            last = last.pre;
        }
        //演示链表的添加对象/数据,是多么方便
        //需求:是在 tom ---------- yzj之间,插入一个对象 Smith

        //1. 先创建一个 Node 节点,name 就是 smith
        Node smith = new Node("smith");
        //2. 下面就是把smith添加到 tom和yzj 中间
        smith.next = yzj;
        smith.pre = tom;
        yzj.pre = smith;
        tom.next = smith;

        //让first再次指向jack
        first = jack;//让first引用指向jack,就是双向链表的头结点
        //查看插入
        System.out.println("插入后顺序");
        while(true) {
            if (first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }

        //让last再次指向yzj
        last = yzj;
        System.out.println("插入后倒序");
        while(true) {
            if (last == null) {
                break;
            }
            //输出first 信息
            System.out.println(last);
            last = last.pre;
        }

    }
}
//定义一个Node类,Node对象 表示双向链表的一个结点
class Node {
    public Object item;
    public Node next;
    public Node pre;

    public Node(Object item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Node name= " + item;
    }
}

3. LinkedList底层结构

LinkedList增删改查案例
import java.util.Iterator;
import java.util.LinkedList;

/**
 * LinkedList 怎删改查案例
 **/
public class LinkedListCRUD {
    public static void main(String[] args) {

        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println("linkedList= " + linkedList);

        //演示删除一个结点
        linkedList.remove();//默认删除的是第一个
        System.out.println("linkedList= " + linkedList);

        //修改某个结点对象
        linkedList.set(1, 99);
        System.out.println("linkedList= " + linkedList);

        //得到某个结点对象
        Object o = linkedList.get(1);
        System.out.println(o);

        //因为LinkedList 是实现了List接口,遍历方式
        System.out.println("======遍历迭代器=========");
        Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println("next= " + next);
        }

        System.out.println("=======增强for循环========");
        for (Object o1 : linkedList) {
            System.out.println("o1= " + o1);
        }

        System.out.println("===========普通for循环===========");
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println(linkedList.get(i));
        }

        //源码阅读
        /* 1. LinkedList linkedList = new LinkedList();
            public LinkedList() {}
            2. 这时 linkedList 的属性 first = null
            3. 执行 添加
            public boolean add(E e) {
                linkLast(e);
                return true;
            }
            4. 将新的结点,加入到双向链表的最后
            void linkLast(E e) {
                final Node<E> l = last;
                final Node<E> newNode = new Node<>(l, e, null);
                last = newNode;
                if (l == null)
                    first = newNode;
                else
                    l.next = newNode;
                size++;
                modCount++;
    }
         */
        /*
            linkedList.remove();//默认删除的是第一个
            1. 执行
                public E remove() {
                    return removeFirst();
            }
            2. 执行
                public E removeFirst() {
                final Node<E> f = first;
                if (f == null)
                    throw new NoSuchElementException();
                return unlinkFirst(f);
            }
            3. 执行 unlinkFirst(f), 将f指向链表的第一个结点,然后将他拿掉
            private E unlinkFirst(Node<E> f) {
                // assert f == first && f != null;
                final E element = f.item;
                final Node<E> next = f.next;
                f.item = null;
                f.next = null; // help GC
                first = next;
                if (next == null)
                    last = null;
                else
                    next.prev = null;
                size--;
                modCount++;
                return element;
            }

         */
    }
}

4. ArrayList和LinkedList的比较

底层结构增删的效率改查的效率
ArrayList可变数组较低
数组扩容
较高
LinkedList可变数组较高,通过链表追加较低
如何选择ArrayList和LinkedList:
  1. 如果我们改查操作多,选择ArrayList
  2. 如果我们增删操作多,选择LinkedList
  3. 一般来说,在程序中,80% - 90%都是查询,因此大部分情况下会选择ArrayList
  4. 在一个项目中,根据业务灵活选择,也可能是这样,一个模块使用的是ArrayList,另外一个模块是LInkedList,也就是说,要根据业务来选择

07. Set接口和常用方法

1. Set接口基本介绍

  1. 无序(添加和取出的顺序不一样),没有索引
  2. 不允许重复元素,所以最多包含一个null
  3. JDK API中Set接口实现的常用类有:
    TreeSet HashSet

2. Set接口的常用方法

和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样

3. Set接口的遍历方式

  1. 可以使用迭代器
  2. 增强for
  3. 不能使用索引的方式来获取
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * set接口,常用方法
 **/
@SuppressWarnings("all")
public class SetMethod {
    public static void main(String[] args) {

        //1. 以Set接口的实现类 HashSet 来讲解Set 接口的方法
        //2. set接口的实现类对象(Set接口对象),不能存放重复的元素,可以添加一个null
        //3. set接口存放数据是无序的(即添加和取出的顺序不一致)
        //4. 注意:取出的顺序虽然不是添加的顺序,但是它固定
        Set set = new HashSet();
        set.add("john");
        set.add("lucy");
        set.add("john");//重复
        set.add("jack");
        set.add("hsp");
        set.add("mary");
        set.add(null);//
        set.add(null);//再次添加 null
        //测试固定
        for (int i = 0; i < 10; i++) {
            System.out.println(set);
        }
        System.out.println(set);

        //遍历
        // 方式1:使用迭代器
        System.out.println("===========使用迭代器===========");
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj= " + obj);
        }
        //方式2;使用增强for
        for (Object o : set) {
            System.out.println("o= " + o);
        }


    }
}

08. Set接口实现类-HashSet

1. HashSet全面说明

  1. HashSet实现了Set接口
  2. HashSet实际上是HashMap,源码:
    public HashSet() {
        map = new HashMap<>();
    }
    
  3. 可以存放null值,但是只能有一个null
  4. HashSet不保证元素是有序的,取决于hash后,再确定索引结果(存入和取出的顺序不一致)
  5. 不能有重复元素/对象。在前面Set使用已经讲过
import java.util.HashSet;
import java.util.Set;

/**
 * Set接口实现类 -> HashSet
 **/
@SuppressWarnings("all")
public class HashSet_ {
    public static void main(String[] args) {
        Set hashSet = new HashSet();
        //1. 构造器的源码
        /*
         public HashSet() {
            map = new HashMap<>();
        }
         */
        //2. HashSet 可以存放null,但是只能有一个null,即元素不能重复
        hashSet.add(null);
        hashSet.add(null);
        System.out.println(hashSet);
    }
}

2. HashSet案例说明

import java.util.HashSet;

/**
 * HashSet案例
 **/
public class HashSet01 {
    public static void main(String[] args) {
        HashSet set = new HashSet();
        //说明
        //1. 在执行 add 方法后,会返回一个 boolean 值
        //2. 如果添加成功,返回 true, 否则返回 false
        //3. 可以通过 remove 指定删除哪个对象
        System.out.println(set.add("john"));//T
        System.out.println(set.add("lucy"));//T
        System.out.println(set.add("john"));//F
        System.out.println(set.add("jack"));//T
        System.out.println(set.add("Rose"));//T
        set.remove("john");
        System.out.println("set=" + set);//3 个

        set = new HashSet();
        System.out.println("set=" + set);//0
        //4 Hashset 不能添加相同的元素/数据?
        set.add("lucy");//添加成功
        set.add("lucy");//加入不了
        set.add(new Dog("tom"));//OK
        set.add(new Dog("tom"));//Ok
        System.out.println("set=" + set);

        //经典面试题,要理解底层结构就会知道
        set.add(new String("hsp"));//ok
        set.add(new String("hsp"));//加入不了. System.out.println("set=" + set);
        System.out.println(set);
    }
}
class Dog {//定义了一个dog类
    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

3. HashSet底层机制说明

​ 分析HashSet底层是HashMap,HashMap底层是(数组 + 链表 + 红黑树)

1. 模拟简单的数组 + 链表结构
package com.yzjedu.set_;

/**
 * HashSet底层结构
 **/
public class HashSetStructure {
    public static void main(String[] args) {
        //模拟一个HashSet底层(HashMap 的底层结构)

        //1. 创建一个数组,数组的类型是Node
        //2. 有些人,直接把 Node[] 数组称为表
        Node table[] = new Node[16];
        System.out.println("table= " + table);
        //3. 创建结点
        Node john = new Node("john", null);
        table[2] = john;
        Node jack = new Node("jack", null);
        john.next = jack;//将jack 结点挂载到 john
        Node rose = new Node("rose", null);
        jack.next = rose;//将rose 结点挂载到 jack

        Node lucy = new Node("lucy", null);
        table[3] = lucy;//把lucy放到 table表索引为3的地方
        System.out.println("table= " + table);
    }
}
class Node { //结点,存储数据,可以指向下一个结点,从而形成链表
    Object item;//存放数据
    Node next;//指向下一个结点

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}

通过自己加入断点调试,你会对其有一点理解

2. 分析HashSet的添加元素底层是如何实现的
  1. HashSet底层是HashMap
  2. 添加一个元素时,先得到hash值 - 会转成 -> 索引值
  3. 找到存储数据表table,看这个索引位置是否已经有存放的元素
  4. 如果没有,直接加入
  5. 如果有调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
  6. 在Java8中,如果一条链表的元素个数达到 TREEIFY_THRESHOOLD(默认是8),并且table大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化
  7. 我参与了公司正在进行的自动化生产线优化项目。通过分析生产流程、引入新的数控技术和自动化设备,我们成功提高了生产效率,并降低了生产过程中的误差率。
3. 案例
import java.util.HashSet;

/**
 * HashMap底层机制
 **/
@SuppressWarnings("all")
public class HashSetSource {
    public static void main(String[] args) {

        HashSet hashSet = new HashSet();
        hashSet.add("java");//到此位置,第一次add分析完毕
        hashSet.add("php");//到此为止,第二次add分析完毕
        hashSet.add("java");
        System.out.println("set= " + hashSet);
        /*
        源码解读
        1.执行构造器
         public HashSet() {
            map = new HashMap<>();
        }
        2.执行 add()
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;//PRESENT:private static final Object PRESENT = new Object();
        }
        3.执行 put(),该方法会执行 hash(key) 得到key的哈希值 算法 (h = key.hashCode()) ^ (h >>> 16)
        public V put(K key, V value) { //key = "java" value = "PRESENT" 共享
            return putVal(hash(key), key, value, false, true);
        }
        4.执行 putVal
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
            //table 就是 HashMap的一个数组,类型是 Node[]
            //if 语句表示如果当前的table 是null,或者大小 = 0
            //就是第一次扩容,到16
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            //(1)根据key,得到hash 去计算key应该存放到table表的哪个索引位置
            //并把这个位置的对象,赋值给p
            //(2)判断p 是否为null
            //(2.1)如果p 为null,表示还没有存放元素,就创建一个新的结点
            //(2.2)就放在该位置 table[i] = newNode(hash, key, value, null)
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
            //开发技巧:在需要局部变量的时候,再创建
                Node<K,V> e; K k;
                //如果索引位置对应的链表的第一个元素和准备添加的key的hash值一样
                //并且满足下面两个条件之一:
                //1. 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象
                //2. p 指向Node 结点的 key 的equals() 和准备加入的key比较后相同
                //就不能加入
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                //再判断 p 是不是红黑树
                //如果是一颗红黑树,就调用 putTreeVal,来进行添加
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                else { //如果当前table对应的索引位置,已经是一个链表,就是要for循环比较
                        //(1) 依次和该链表的每一个元素比较后,都不相同,则加入链表的最后
                        //   注意在把元素添加到链表后,立即判断,该链表时候否已经达到8个结点
                        //   , 就调用 treeifyBin() 对当前这个链表进行树化进行判断,判断条件
                        //  if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                        //      resize();
                        //   如果上面条件成立,就先扩容
                        //   只有上面条件不成立时,才进行转成红黑树
                        //(2) 依次和该链表的每一个元素比较过程总,如果有相同情况,就直接break
                    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;
        }
         */
    }
}
4. 分析HashSet的扩容和转成红黑树机制
  1. HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16 * 加载因子 (loadFactor)是0.75 = 12
  2. 如果table数组使用到了临界值12,就会扩容到 16 * 2 = 32 ,新的临界值就是 32 * 0.75 = 24,以此类推
  3. 在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
import java.util.HashSet;

/**
 * 分析HashSet的扩容和转成红黑树机制
 **/
@SuppressWarnings({"all"})
public class HashSetIncrement {
    public static void main(String[] args) {
        /*
        1. HashSet底层是HashMap,第一次添加时,table数组扩容到16,
        临界值(threshold)是16 * 加载因子 (loadFactor)是0.75 = 12
        2. 如果table数组使用到了临界值12,就会扩容到 16 * 2 = 32 ,
        新的临界值就是 32 * 0.75 = 24,以此类推
         */
        HashSet hashSet = new HashSet();
//        for (int i = 1; i <= 100; i++) {
//            hashSet.add(i);//1, 2, 3, 4
//        }
        
       //在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8),并且		//table的大小 >=  MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑				//树),否则仍然采用数组扩容机制
        for (int i = 1; i < 12; i++) {
            hashSet.add(new A(i));
        }
        System.out.println("hashset= " + hashSet);
    }
}
class A {
    private int n;

    public A(int n) {
        this.n = n;
    }


    @Override
    public int hashCode() {
        return 100;
    }
}

08. Set接口实现类-LInkedHashSet

1. LInkedHashSet的全面说明

  1. LInkedHashSet是HashSet的子类
  2. LInkedHashSet底层是一个LInkedHashMap,底层维护了一个数组+双向链表
  3. LInkedHashSet 根据元素的hashCode值来决定元素的存储位置,同时用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  4. LInkedHashSet不允许添加重复的元素

2. 说明添加

  1. 在LInkedHashSet 中维护了一个hash表和双向链表(LinkedHashSet 有 head 和 tail)
  2. 每一个节点有 before 和 after 属性,这样可以形成双向链表
  3. 在添加一个元素时,先求hash值,再求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加,原理和HashSet一样)
  4. 这样的话我们遍历LInkedHashSet 也能确保插入顺序和遍历顺序一致
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * LinkedHashSet 底层机制
 **/
public class LinkedHashSet_ {
    public static void main(String[] args) {
        //分析一下LinkedHashSet的底层机制
        Set set = new LinkedHashSet();
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("刘", 1001));
        set.add(123);
        set.add("YZJ");
        //通过输出可以查看输出是有序的
        System.out.println("set= " + set);
        //1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致
        //2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
        //3. LinkedHashSet 底层结构(数组+双向链表)
        //4. 添加第一次时,直接将 数组table 扩容到 16,存放的结点类型是 LinkedHashMap$Entry
        //5. 数组是 HashMap$Node[] 存放的元素/数据是 LInkedHashMap$Entry 类型
        /*
            //继承关系是内部类 完成的
            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);
        }
    }
         */
    }
}
class Customer {
    private String name;
    private int no;

    public Customer(String name, int no) {
        this.name = name;
        this.no = no;
    }

}

09. Map接口和常用方法

1. Map接口实现类的特点(很实用)

注意:这里讲的是JDK8的Map接口特点

  1. Map和Collection并列存在。用于保存具有映射关系的数据:Key-Value
  2. Map中的Key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  3. Map中的key不允许重复,原因和HashSet一样,前面分析过
  4. Map中的value可以重复
  5. Map的key可以为null,value也可以为null,注意key为null,只能有一个,value为null可以多个。
  6. 常用String类作为Map的key
  7. key和value之间存在一对一关系,即通过指定的key总能找到对应的value
import java.util.HashMap;
import java.util.Map;

/**
 * Map接口01
 **/
public class Map_ {
    public static void main(String[] args) {
        //Map接口实现类的特点
        //1. Map和Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
        //2. Map中的Key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
        //3. Map中的key不允许重复,原因和HashSet一样,前面分析过
        //4. Map中的value可以重复
        //5. Map的key可以为null,value也可以为null,注意key为null,只能有一个,value为null可以多个。
        //6. 常用String类作为Map的key
        //7. key和value之间存在一对一关系,即通过指定的key总能找到对应的value
        Map map = new HashMap();
        map.put("no1","路明非");//k - v
        map.put("no2", "诺诺");//k - v
        map.put("no1",  "凯撒");//当有相同的key时,就等价于替换
        map.put("no3", "路明非");//值可以重复
        map.put(null, null);
        map.put(null, "abc");//abc 会替换到原先的空值
        map.put("no4",  null);//值的空可以有多个
        map.put("no5", null);
        map.put(1, "源稚生");//key 可以是其他但是常用String类来作为他的key
        map.put(new Object(), "源稚女");
        
        //通过get 方法,传入可以,会返回对应的value
        System.out.println(map.get("no1"));

        System.out.println("map= " + map);
    }
}
  1. Map存放数据的key-value,一对k-v是放在一个Node中的,有因为Node实现了 Entry 接口,有些书上也说一对k-v就是一个Entry
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
 * Map接口底层解释
 **/
public class MapSource_ {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("no1","路明非");//k - v
        map.put("no2", "诺诺");//k - v
        map.put(new Car(), new Person());//k - v

        //1. k-v 最后是 HashMap$Node node = new Node<>(hash, key, value, next)
        //2. 为了方便程序员的遍历,还会创建 EntrySet集合,该集合存放的元素的类型 Entry, 而一个Entry
        //   对象k,v EntrySet<Entry<K,V>> 即  transient Set<Map.Entry<K,V>> entrySet;
        //3. entrySet 中, 定义的类型是 Map.Entry,但是实际上还是HashMap$Node
        //   这是因为HashMap$Node implements Map.Entry
        //4. 这样当把 HashMap$Node 对象 存放到 entrySet 就方便我们遍历,因为Map.Entry 提供了重要方法
        //   K getKey(); V getValue();
        Set set = map.entrySet();
        System.out.println(set.getClass());// HashMap$EntrySet
        for (Object obj : set) {
            //为了从HashMap$Node中取出k-v
            //1. 向下转型
            Map.Entry entry = (Map.Entry)obj;
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
        Set set1 = map.keySet();
        System.out.println(set1.getClass());
        Collection values = map.values();
        System.out.println(values.getClass());
    }
}
class Car {

}
class Person {

}

2. Map接口的常用方法

  1. put:添加
  2. remove:根据键删除映射关系
  3. get:根据键获取值
  4. size:获取元素个数
  5. isEmpty:判断个数是否为零
  6. clear:清除
  7. containsKey:查找键是否存在
import java.util.HashMap;
import java.util.Map;

/**
 * Map接口常用方法
 **/
public class MapMethod {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("邓超", new Book("", 100));//OK
        map.put("邓超", "孙俪");//替换-> 一会分析源码
        map.put("王宝强", "马蓉");//OK
        map.put("宋喆", "马蓉");//OK
        map.put("刘令博", null);//OK
        map.put(null, "刘亦菲");//OK
        map.put("鹿晗", "关晓彤");//OK
        map.put("yzj", "yzj 的老婆");
        System.out.println("map=" + map);
        // remove:根据键删除映射关系
        map.remove(null);
        System.out.println("map=" + map);
        // get:根据键获取值
        Object val = map.get("鹿晗");
        System.out.println("val=" + val);
        // size:获取元素个数
        System.out.println("k-v=" + map.size());
        // isEmpty:判断个数是否为 0
        System.out.println(map.isEmpty());//F
        // clear:清除 k-v
        //map.clear();
        System.out.println("map=" + map);
        // containsKey:查找键是否存在
        System.out.println("结果=" + map.containsKey("hsp"));//T
    }
}
class Book {
    private String name;
    private int num;
    public Book(String name, int num) {
        this.name = name;
        this.num = num;
    }
}

3. Map接口遍历方法

1. 与之相关的方法
  1. containsKey:查找键是否存在
  2. keySet:获取所有的键值
  3. entrySet:获取所有的关系
  4. values:获取所有的值
2. 案例
import java.util.*;

/**
 * Map遍历方式案例
 **/
public class MapFor {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("邓超","孙俪");
        map.put("王宝强","马蓉");
        map.put("宋喆","马蓉");
        map.put("刘令博",null);
        map.put(null,"刘亦菲");
        map.put("鹿晗","关晓彤");

        //第一组:先取出所有的Key,通过Key取出对应的Value值
        Set keyset = map.keySet();
        //(1)增强for
        for (Object key : keyset) {
            System.out.println(key + "-" + map.get(key));
        }
        System.out.println("=============================");
        //(2)迭代器
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(key + "-" + map.get(key));

        }
        System.out.println("---先取出所有的value----");
        //第二组:先取出所有的values
        Collection values = map.values();
        //这里可以使用所有的Collection遍历方式
        //(1)增强for
        for (Object value : values) {
            System.out.println(value);
        }
        System.out.println("===================");
        //(2)迭代器
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()) {
            Object value = iterator1.next();
            System.out.println(value);
        }
        System.out.println("------通过EntrySet-------------");
        //第三组:通过EntrySet 来获取k - v
        Set entrySet = map.entrySet();
        //(1)增强for
        for (Object entry : entrySet) {
            //将entry 转成 Map.Entry
            Map.Entry m = (Map.Entry)entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        System.out.println("===================");
        //(2)迭代器
        Iterator iterator2 = entrySet.iterator();
        while (iterator2.hasNext()) {
            Object entry = iterator2.next();
            //System.out.println(next.getClass());//HashMap$Node - 实现 - Map.Entry (getKey, getValue)
            Map.Entry m = (Map.Entry)entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
    }
}
4. HashMap小结
  1. Map接口的常用实现类:HashMap、Hashtable和Properties
  2. HashMap是 Map 接口使用频率最高的的实现类
  3. HashMap是 key-val 对的方式来存储数据(HashMap$Node)
  4. key 不能重复,但是值可以重复,允许使用null键和null值
  5. 如果添加相同的key,则会覆盖原来的key - val,等同于 修改(可以不会替换,val会替换)
  6. 与HashSet一样,不保证映射顺序,因为底层是以hash表的方式来存储的(jdk8的hashMap底层 数组+链表+红黑树
  7. HashMap没有实现同步,因此是线程不安全的(没有synchronized)

10. HashMap底层机制和源码剖析

1. 注意

  1. (k,v)是一个Node实现了Map.Entry<K,V>, 查看HashMap的源码可以看到
  2. jdk7.0的Hashmap底层实现[数组+链表],jdk8.0 底层[数组+链表+红黑树]

2. 扩容机制

  1. HashMap底层维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因子(loadfactor)初始化为0.75
  3. 当添加key-val时,通过key的hash值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该元素有元素,则判断该元素的key是否和准备加入的key相等,相等则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应的处理。如果添加时发现容量不够,则需要扩容。
  4. 第一次添加,则需要扩容table的容量到16,临界值(threshold)为12
  5. 以后再扩容,则需要扩容table的容量为原来的2倍,临界值为原来的两倍,即24,一次类推

在Java8中,如果一条链表的元素超过 TREELFY_THRESHOULD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

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

/**
 * HashMap底层机制和源码剖析01
 **/
@SuppressWarnings({"all"})
public class HashMapSource1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java", 10);//ok
        map.put("php", 20);//ok
        map.put("java", 20);//替换
        System.out.println("map= "+ map);

        //解读:
        //1. 执行构造器 new HashMap()
        //   初始化加载因子 loadfactor = 0.75
        //   HashMap$Node[] table = null
        //2. 执行put, 会调用 hash方法, 计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16)
        /*
        public V put(K key, V value) {//K = "java" value = 10
            return putVal(hash(key), key, value, false, true);
        }

        3. 执行putVal方法
         final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
            //如果底层table 数组为null, 或者 Length = 0, 就扩容到16
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            //取出hash值对应的table表的索引位置的Node,如果为null ,就直接把加入的k-v
            //, 创建成一个Node, 加入该位置即可
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                Node<K,V> e; K k;//辅助变量
                //表示如果table 表索引位置key的hash值相同和新的key的hash相同,
                并且满足(现有的结点的key和准备添加的key可以是同一个对象 || equals返回真)
                //就认为不能加入新的key - value
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                // 如果当前table的已有Node 是红黑树,就按照红黑树的方式处理
                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);
                            //加入后,判断当前链表的个数,是否已经到达8个,后
                            //就调用 treeifyBin 方法进行红黑树的转化工作
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&//如果在循环比较过程中,发现有相同,就break,就只是替换key - value
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                if (e != null) {
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;// 每增加一个Node,就是size++
            if (++size > threshold)//如果size > 临界值,就执行扩容方法 resize()
                resize();
            afterNodeInsertion(evict);
            return null;
        }

        5. 关于树化(转成红黑树)
        //如果table 为null,或者大小还没有达到64,暂时不树化,而是进行扩容
        //否则真正树化 - 剪枝
         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();
         */
    }
}

3. 模拟HashMap触发扩容、树化情况

import java.util.HashMap;
import java.util.Objects;

/**
 * HashMap底层机制和源码剖析2
 **/
public class HashMapSource2 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        for (int i = 1; i <= 12; i++) {
//            hashMap.put(new A(i), "hello");
            hashMap.put(i, "hello");
        }
        System.out.println("hashMap= " + hashMap);
        hashMap.put("aaa","bbb");
    }
}
class A {
    private int num;

    public A(int num) {
        this.num = num;
    }

    //所有的hashCode都是100
//    @Override
//    public int hashCode() {
//        return 100;
//    }

    @Override
    public String toString() {
        return "A{" +
                "num=" + num +
                '}';
    }
}

11. Map接口实现类-Hashtable

1. HashTable的基本介绍

  1. 存放的元素时键值对:即k - v
  2. Hashtable的键和值都不能为null,否则会抛出NullPointerException 异常
  3. Hashtable使用方法上基本和HashMap一样
  4. Hashtable是线程安全(synchronized)的,HashMap是线程不安全的

2. 介绍和扩容案例

import java.util.Hashtable;

/**
 * Hashtable01
 **/
@SuppressWarnings("all")
public class HashTableExercise {
    public static void main(String[] args) {
        Hashtable table = new Hashtable();//ok
        table.put("john", 100); //ok
//        table.put(null, 100); //异常
//        table.put("john", null);//异常
        table.put("lucy", 100);//ok
        table.put("lic", 100);//ok
        table.put("lic", 88);//替换
        table.put("hello01",1);
        table.put("hello02",1);
        table.put("hello03",1);
        table.put("hello04",1);
        table.put("hello05",1);
        table.put("hello06",1);
        table.put("hello07",1);
        System.out.println(table);

        //1. 底层有数组 Hashtable#Entry[] 初始化大小为11
        //2. 临界值 threshold 8 = 11 * 0.75
        //3. 扩容机制:按照自己的扩容机制来进行
        //4. 执行 方法addEntry(hash, key, value, index); 添加k-v 封装到Entry
        //5. 当if (count >= threshold)满足时,就进行扩容
        //6. 按照int newCapacity = (oldCapacity << 1) + 1; 的大小扩容

    }
}

3. Hashtable和HashMap对比

HashMapHashtable
版本1.21.0
线程安全(同步)不安全安全
效率较低
允许null键和null值可以不可以

12. Map接口实现类-Properties

1. Properties基本介绍

  1. Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对形式来保存数据
  2. 他的使用特点和HashTable类似
  3. Properties 还可以用于 从xxx.properties 文件中,加载数据到Properties类对象,并进行读取和修改
  4. 说明:工作后 xxx.properties 文件通常作为配置文件,这个知识点在IO流举例。(后面的文章会发布)

2. 案例

package com.yzjedu.map_;

import java.util.Properties;

/**
 * Map接口实现类-Properties
 **/
public class Properties_ {
    public static void main(String[] args) {
        //1. Properties 继承 Hashtable
        //2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
        //增加
        Properties properties = new Properties();
        //properties.put(null, "abc");//抛出 空指针异常
        //properties.put("abc", null); //抛出 空指针异常
        properties.put("john", 100);//k-v
        properties.put("lucy", 100);
        properties.put("lic", 100);
        properties.put("lic", 88);//如果有相同的 key , value 被替换
        System.out.println("properties=" + properties);
        //通过 k 获取对应值
        System.out.println(properties.get("lic"));//88
        //删除
        properties.remove("lic");
        System.out.println("properties=" + properties);
        //修改
        properties.put("john", "约翰");
        System.out.println("properties=" + properties);
    }
}

13. 总结-开发中如何选择集合实现类

1. 主要内容

​ 在开发中,选择什么集合实现类,主要取决于业务操作特点,然后更具集合实现类特性进行选择,分析如下:

  1. 先判断存储的类型(一组对象或一组键值对)
  2. 一组对象[单例]:Collection接口
    1. 允许重复:List
      1. 增删多:LInkedList(底层维护了一个双向链表)
      2. 改查多:ArrayList(底层维护Object类型的可变数组)
    2. 不允许重复:set
      1. 无序:HashSet(底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)
      2. 排序:TreeSet(插入和取出的顺序一致:LInkedHashSet,维护数组+双向链表)后边举例
      3. 插入和取出顺序一致:LInkedHashSet,维护数组+双向链表
  3. 一组键值对[双链]:Map
    1. 键无序:HashMap(底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树)
    2. 键排序:TreeMap 后边举例
    3. 键插入和取出顺序一致:LInkedHashMap
    4. 读取文件:Properties

2. TreeSet举例

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

/**
 * TreeSet讲解
 **/
public class TreeSet_ {
    public static void main(String[] args) {

        //1. 当我们使用无参构造器,创建TreeSet时,任然是无序的
        //2. 按照字母表首字母排序,使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)
        // 并指定顺序规则
        //3. 看源码

//        TreeSet treeSet = new TreeSet();
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //下面调用String的 compareTo方法进行进行字符串大小比较
                return ((String)o2).compareTo((String)o1);
            }
        });
        //添加数据
        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("sp");
        treeSet.add("a");
        System.out.println("TreeSet= " + treeSet);
        //源码解读:
        //1. 构造器把传入的比较对象,赋给了 TreeSet的底层的 TreeSetMap属性this.comparator
        /*
            public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
        //2. 在 调用 TreeSet.add("tom"), 在底层会执行到
             if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
                do {
                    parent = t;
                    //动态绑定到我们的匿名内部类
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else //如果相等,即返回0,这个Key就没有加入
                        return t.setValue(value);
                } while (t != null);
            }
         */
    }
}

3. TreeMap举例

import java.util.Comparator;
import java.util.TreeMap;

/**
 * TreeMap解读
 **/
@SuppressWarnings("all")
public class TreeMap_ {
    public static void main(String[] args) {

        //使用默认构造器,创建TreeMap,无序的(也没有排序)
        /*
            要求:按照传入的k(String) 的大小进行排序
         */
//        TreeMap treeMap = new TreeMap();
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //按照传入的k(String) 的大小进行排序
                //从小到大
//                return ((String)o1).compareTo((String)o2);
                //按照k(String) 的长度大小来排序
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("krastina", "克瑞斯提诺");
        treeMap.put("smith", "史密斯");
        System.out.println("treeMap= " + treeMap);
        /*
        解读源码:
        1. 构造器,把传入的实现了 Comparator接口的匿名内部类(对象),传给TreeMap的comparator
            public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
        2. 调用put方法
        2.1 第一次添加,把 k-v 封装到 Entry对象,放入root
             Entry<K,V> t = root;
             if (t == null) {
                compare(key, key); // type (and possibly null) check
    
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
        2.2 以后添加
             Comparator<? super K> cpr = comparator;
             if (cpr != null) {
                do { //遍历所有的key
                    parent = t;
                    cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类compare
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else //如果遍历过程中,发现准备添加Key 和当前已有Key 相等,就不添加
                        return t.setValue(value);
                } while (t != null);
            }
         */
    }
}

13. Collections工具类

1. Collections工具类介绍

  1. Collections 是一个操作 Set、List 和 Map等集合的工具类
  2. Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
1. 排序操作
  1. reverse(List): 翻转List中元素的顺序
  2. shuffle(List): 对List集合元素进行随机排序
  3. sort(List): 根据元素的自然顺序对指定List结合元素按升序排序
  4. sor(List, Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
  5. swap(List, int, int): 将指定List集合中的i处元素和j元素进行交换
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * Collections工具类
 **/
public class Collections_ {
    public static void main(String[] args) {
        //创建ArrayList集合,用于测试
        ArrayList arrayList = new ArrayList();
        arrayList.add("tom");
        arrayList.add("smith");
        arrayList.add("nono");
        arrayList.add("milan");
//        1. reverse(List): 翻转List中元素的顺序
        Collections.reverse(arrayList);
        System.out.println(arrayList);

//        2. shuffle(List): 对List集合元素进行随机排序
//        for (int i = 0; i < 5; i++) {
//            Collections.shuffle(arrayList);
//            System.out.println(arrayList);
//        } //出现的每次结果都不一样,用于抽奖机制等
//        Collections.shuffle(arrayList);
//        System.out.println(arrayList);

//        3. sort(List): 根据元素的自然顺序对指定List结合元素按升序排序
        Collections.sort(arrayList);
        System.out.println("自然排序后");
        System.out.println("arrayList= " + arrayList);
//        4. sor(List, Comparator): 根据指定的Comparator产生的顺序对List集合元素进行排序
        //如果我们希望按照字符串长度大小排序
        Collections.sort(arrayList, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        System.out.println("字符串长度大小排序= \n" + arrayList);

//        5. swap(List, int, int): 将指定List集合中的i处元素和j元素进行交换
        //比如
        Collections.swap(arrayList, 0, 1);
        System.out.println("交换后的情况:");
        System.out.println("arrayList= " + arrayList);
    }
}
2. 查找替换
  1. Object max(Collection): 根据元素的自然顺序, 返回给定的集合中的最大元素
  2. Object max(Collection, Comparator): 根据Comparator指定的顺序,返回给定集合中的最大元素
  3. Object min(Collection)
  4. Object min(Collection, Comparator)
  5. int frequency(Collection, Object): 返回指定集合中指定元素的出现次数
  6. void copy(List dest, List src): 将src中内容复制到dest中
  7. boolean replaceAll(List list, Object oldVal, new Val): 使用新值替换List对象的所有旧值
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * Collections工具类
 **/
public class Collections01_ {
    public static void main(String[] args) {
        //创建ArrayList集合,用于测试
        ArrayList list = new ArrayList();
        list.add("tom");
        list.add("smith");
        list.add("nono");
        list.add("milan");
        //Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
        System.out.println("自然顺序最大元素=" + Collections.max(list));
        //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
        //比如,我们要返回长度最大的元素
        Object maxObject = Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        System.out.println("长度最大的元素=" + maxObject);
        //Object min(Collection)
        //Object min(Collection,Comparator)
        //上面的两个方法,参考 max 即可
        //int frequency(Collection,Object):返回指定集合中指定元素的出现次数
        System.out.println("tom 出现的次数=" + Collections.frequency(list, "tom"));
        //void copy(List dest,List src):将 src 中的内容复制到 dest 中
        ArrayList dest = new ArrayList();
        //为了完成一个完整拷贝,我们需要先给 dest 赋值,大小和 list.size()一样
        for (int i = 0; i < list.size(); i++) {
            dest.add("");
        }
        //拷贝
        Collections.copy(dest, list);
        System.out.println("dest=" + dest);
        //boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
        //如果 list 中,有 tom 就替换成 汤姆
        Collections.replaceAll(list, "tom", "汤姆");
        System.out.println("list 替换后=" + list);
    }
}
  • 46
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值