Java集合专题笔记

Java集合专题笔记

集合体系

集合框架体系

Collection(*)单例集合

在这里插入图片描述

List
  • ArrayList(*)
  • LinkedList
  • Vector(*)
Set
  • HashSet(*)
  • LinkedHashSet
  • TreeSet

Map(双例集合,键值对)

在这里插入图片描述

  • HashMap(*)
  • Hashtable
  • LinkedeHAshMap
  • TreeMap
  • Properties

Collections

Collection接口和常用方法

2022-4-10 学习笔记

Collection接口实现类的特点

public interface Collection<E> extends Iterable<E>
  • collection实现子类可以存放多个元素,每个元素都可以是Object

  • 有些Collection实现的类,可以存放重复的元素,有些不可以

  • 有些Collection实现的类,有些是有序的(List),有些不是有序的(Set)

  • Collection接口没有直接的实现子类,是通过他的子类接口Set和List实现

  • package main.java.com.chapter14.collection;
              
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
              
    /**
     * @Author BaoYuan
     * @CreateDate 2022/4/10 15:35
     * @Version 1.0
     */
    public class CollectionMethod {
        public static void main(String[] args) {
            List list = new ArrayList();
        //    add:添加单个元素
            list.add("数学");
            list.add("英语");
        //    remove:删除指定元素
            list.remove("数学");
            list.remove(0);
        //    contains:查找元素是否存在
            list.contains("数学");
        //    size:获取元素个数
            list.size();
        //    isEmpty:判断是否为空
            list.isEmpty();
        //    clear:清空
            list.clear();
        //    addAll:添加多个元素
            List list2=new ArrayList();
            list2.add("政治");
            list2.add("数据结构");
            list.addAll(list2);
        //    containsAll:查找多个元素是否存在
            list.containsAll(list2);
        //    removeAll:删除多个元素
            list.removeAll(list2);
        //    ArraysList演示
        }
    }
              
    

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

  • Iterator对象称为迭代器,主要用于便利Collection集合中的元素
  • 所有实现了Collection接口的集合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象,即可返回一个迭代器
  • Iterator的结构
  • Iterator仅用于遍历集合,Iterator本身并不存放对象
  • 快捷键:itit
迭代器的执行原理
Iterator iterator = coll.iterator;//得到一个集合的迭代器
// hasNext();判断是否还有下一个元素
while(iterator.hasNext()){
    //next():①指针下移 ②将下移以后集合位置上的元素返回
    System.out.println(iterator.next());
}//在调用iterator.next()方法之前必须要调用tierator.hasNext()方法进行检测,若不调用,且下一条记录无效,直接调用tierator.next()方法会抛出 NoSuchElementException异常

在这里插入图片描述

代码演示
package main.java.com.chapter14.collection;

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

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/10 16:04
 * @Version 1.0
 */
public class CollectionIterator {
    public static void main(String[] args) {
        Collection list =new ArrayList();
        list.add(new Book("三锅","张三"));
        list.add(new Book("税负","三"));
        list.add(new Book("就覅","里斯"));
        Iterator iterator=list.iterator();
        while (iterator.hasNext()) {//判断是否还有数据
            //返回下一个元素,类型是Object
            Object next =  iterator.next();
            System.out.println("book="+next);
        }
        // 循环结束,iterator迭代器指向最后一个元素
        // 是用 iterator.next() 会抛出 NoSuchElementException异常
        // 再次便利使用,iterator = col.iterator() 即可
    }
}

class Book{
    private String name;
    private String author;

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

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

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

Collection接口遍历元素方式2一增强for循环

​ 增强for循环,可以替代iterator迭代器,特点:增强for循环就是简化了iterator,本质一样,只能用于遍历集合或数组。

​ 快捷键:I

​ 基本语法:

for(String(元素类型) obj(元素名):list(集合名或数组)){}

课堂练习

请编写程序CollectionExercise.java
1.创建3个 Dog {name, age}对象,放入到ArrayList 中,赋给List 引用2.用迭代器和增强for循环两种方式来遍历
3.重写Dog的toString方法,输出name和age

List接口和常用方法

List接口时Collection接口的子接口

  • List集合类中元素有序(添加于取出的顺序一致,且可重复)
  • List集合中的每个元素都有其对应的顺序索引,即支持索引
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
  • JDK API中List接口实现类有:AbstractList,AbstractSequentialList,ArrayList,AttributeList,CopyWriteArrayList,LinkedList,RoleList,RoleUnresolveList,Stack,Vector
List常用方法

代码演示

package main.java.com.chapter14.list;

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

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/10 16:36
 * @Version 1.0
 */
public class ListMethod {
    public static void main(String[] args) {
        List list=new ArrayList();
        list.add("张三丰");
        list.add("张三丰");
        list.add("贾宝玉");
        //void add(int index,Object ele) 在index位置插入ele
        list.add(1,"张三");

        //boolean addAll(int index,Collection eles) 从index位置开始将eles所有元素添加进来
        List list2 = new ArrayList();
        list2.add("hs");
        list2.add("aa");
        list.addAll(1,list2);
        //int indexOf(Object obj) 返回obj在当前集合中首次出现的位置
        list.indexOf("张三");
        //int lastIndexOf(Object obj) 返回obj在当前集合中末次出现的位置
        list.lastIndexOf("张三");
        //Object remove(int index) 删除指定index位置的元素,并返回次元素
        list.remove(1);
        //Object set(int index,Object ele) 设置指定位置元素为ele,相当于替换
        list.set(1,"aa");
        //List subList(int fromIndex,int toIndex) 返回从fromIndex到toIndex位置的子集合 [)
        list.subList(0,1);
    }
}

List的三种遍历方式[ArrayList、LinkedList、Vector]
方式一

使用iterator

Iterator iterator = col.iterator;
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
方式二

使用增强for循环

for(String item:list){
    System.out.println(item);
}
方式三

使用普通for

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

在这里插入图片描述
代码实现

package main.java.com.chapter14.list;

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

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/10 17:13
 * @Version 1.0
 */
public class ListExercise02 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Book("三国演义","xxx",100));
        list.add(new Book("红楼梦","xxx",150));
        list.add(new Book("水浒传","xxx",90));
        list.add(new Book("水浒传2","xxx",200));

        sort(list);

        for (Object o :list) {
            System.out.println(o);
        }


    }

    public static void sort(List list){
        int len=list.size();
        for (int i = 0; i <len-1; i++) {
            for (int j = 0; j <len-i-1 ; j++) {
                Book book1=(Book)list.get(j);
                Book book2=(Book)list.get(j+1);
                if(book1.getPrice()>book2.getPrice()){
                    list.set(j,book2);
                    list.set(j+1,book1);
                }
            }
        }
    }
}

class Book{
    private String name;
    private String author;
    private int price;

    public Book(String name, String author, int 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 int getPrice() {
        return price;
    }

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

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

ArrayList注意事项
  • permits all elements,including null ArrayList可以加入null,并且多个
  • ArrayList 是由数组来实现数据存储的
  • ArrayList 基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码,在多线程情况下,不建议使用ArrayList
  • 没有加 synchronized关键字
ArrayList底层结构和源码分析(重点、难点)
  • ArrayList中维护了一个Object类型的数组elementData.[debug 看源码] transient Object[] elementData; -》transient 表示瞬间的、短暂的,表示该属性不会被序列号
  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
  • 如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如需扩容,则直接扩容elementData为1.5倍
添加元素源码分析(扩容机制)
public ArrayList() {
    // 构造器
    // 创建了一个空的 elementData数组={}
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
public static Integer valueOf(int i) {
    // 装箱
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
public boolean add(E e) {
    // 1.先确定是否要扩容
    // 2.然后再执行赋值
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
    // 该方法确定minCapacity
    // 第一次扩容为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
private void ensureExplicitCapacity(int minCapacity){
    modCOunt++;//防止多线程问题,记录倍修改的次数
    if(minCapacity - elementData.length > 0)// 如果elementData的大小不够,就调用grow扩容
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // 1.真扩容方法
    // 2.使用扩容机制来确定要扩容到多大
    // 3.第一次newCapacity = 10
    // 4.第二次即以后,按照1.5倍扩容
    // 5.扩容使用Arrays。copyOf() 保留原来数据
        // overflow-conscious code
        int oldCapacity = elementData.length; //保留之前的容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);// 1.5倍
        if (newCapacity - minCapacity < 0)//如果新的容量比最小的容量小 则直接扩容
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
指定容器大小构造器源码解读
public ArrayList(int initialCapacity) {
    // 调用构造器发生变化
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
Vector底层结构与源码剖析
  • Vector底层也是一个对象数组,protected Object[] elementData

  • Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized

  • public synchronized void addElement(E obj) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = obj;
        }
    
  • 在开发中,需要线程同步安全时,考虑使用Vector

Vector与ArrayList比较
底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,效率高如果有参数构造器1.5倍; 如果无参数:1.第一次10,第二次开始扩容1.5
Vector可变数组jdk1.0安全,效率不高如果无参,默认10倍,满后按2倍扩容,如果指定大小,则每次按2倍扩容
Vercot源码嫉妒

new Vector() 底层

public Vector() {
    this(10);
}
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

Vector.add(i)

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);//去前面的ArrayList类似,确定集合容量是否足够
    elementData[elementCount++] = e;
    return true;
}
//确认容量是否足够
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
//扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList底层结构
  • LinkedList实现了双向链表和双端队列特点
  • 可以添加任意元素,元素可重复,包括null
  • 线程不安全,没实现同步
LinkedList底层操作机制
  • LinkedList底层维护了一个双向链表
  • LinkedList中维护了两个属性frist和last,分别指向首节点和尾节点
  • 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双线链表
  • 所以LinkedList的元素添加和删除,不是通过数组完成,相对来说效率较高
双向链简单实现
package main.java.com.chapter14.list;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/10 22:32
 * @Version 1.0
 */
public class LInkedList01 {
    public static void main(String[] args) {
        Node tom = new Node("tome");
        Node jack = new Node("jack");
        Node Bao = new Node("Bao");
        Node first=new Node(null);
        Node last=new Node(null);

        tom.next=jack;
        jack.next=Bao;

        Bao.pre=jack;
        jack.pre=tom;

        first.next=tom;
        last.pre=Bao;
        Node tem=first.next;
        while(tem!=null){
            System.out.println(tem);
            tem=tem.next;
        }

    }
}

class Node{
    private Object name;
    public Node pre;
    public Node next;

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

    @Override
    public String toString() {
        return "Node{" +
                "name='" + name + '\'' +
                '}';
    }
}
LinkedList的增删改查案例

执行 LinkedList linkedList=new LinkedList();

 public LinkedList() {}
//此时,first与last=null

当 linkedList.add(ele)时,执行

public boolean add(E e) {
    linkLast(e);
    return true;
}
void linkLast(E e) {
    final Node<E> l = last;// 不加final修饰符,l会随着last而修改自己地址
    final Node<E> newNode = new Node<>(l, e, null);//Node(Node<E> prev, E element, Node<E> next)
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
ArrayList与LinkedList比较
底层结构增删的效率改查的效率
ArrayList可变数组较低、数组扩容较高
LinkedList双向链表较高、通过链表追加较低
如何选择ArrayList与LinkedList
  • 改查的操作多,选择ArrayList
  • 增删操作多,选择LinkedList
  • 一般来说,程序80%~90%都是查询,因此大部分都采取ArrayList
  • ArrayList与LinkedList都是线程不安全,最好都在单线程时使用

Set接口和常用方法

2022-4-11学习笔记

Set接口基本介绍

  • 无序(添加和取出顺序不一样,但它是固定的),没有索引
  • 不允许重复元素,所以最多包含一个null
  • JDK API的Set接口实现类主要有:HashSet、TreeSet、LinkedHashSet
  • Set接口和List一样,Set接口也是Collection的子接口,因此常用方法和Collection接口一样
Set接口的遍历方式

同Collection的遍历方式一样,因为Set接口是Collection接口的子接口

  • 可以使用迭代器
  • 增强for
  • 不可以使用索引的方式获取
HashSet全面说明
  • HashSet实现了Set接口

  • HashSet实际上是HashMap

  • public HashSet() {
        map = new HashMap<>();
    }
    
    
  • 可以存放null值,但只能有一个

  • HashSet不保证元素是有序的,取决于hash后,在确定索引的结果

  • 不能有重复元素/对象

简单代码演示
package main.java.com.chapter14.set;

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

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/11 19:45
 * @Version 1.0
 */
public class HashSet_ {
    public static void main(String[] args) {
        Set hashSet = new HashSet();

        hashSet.add("jack");//Ok
        hashSet.add("jack");//No
        hashSet.add(new Dog("tom"));//Ok
        hashSet.add(new Dog("tom"));//Ok
        //经典面试题
        hashSet.add(new String("cat"));//Ok
        hashSet.add(new String("cat"));//No

        System.out.println("set="+hashSet);
    }
}

class Dog{
    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}
HashSet底层机制说明

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

模拟代码演示
package main.java.com.chapter14.set;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/11 20:03
 * @Version 1.0
 */
public class HashSetStructure {
    public static void main(String[] args) {
        //模拟一个HashSet的(HashMap底层)数组+链表
        Node[] table=new Node[16];

        Node jack = new Node("jack", null);
        //将jack放在table下标为2下
        table[2]=jack;
        Node lucy = new Node("lucy", null);
        //使将lucy挂载在jack下
        jack.next=lucy;
        Node tom = new Node("tom", null);
        table[3]=tom;

        System.out.println(table);

    }
}

class Node{
    private String name;
    Node next;

    public Node(String name, Node next) {
        this.name = name;
        this.next = next;
    }
}
HashSet扩容机制
  • HashSet底层使HashMap
  • 添加一个元素时,先得到hash值,会转成->索引值
  • 找到存储数据表table,看这个索引位置是否已经存放有元素
  • 如果没有直接添加
  • 如果有,调用equals()比较(不可的理解成比较内容,每个类有自己的方法,可由程序员重写该方法),如果相同,就放弃添加,如果不同,则添加道最后
  • 在Java8重,如果一条链表元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
源码解读

执行 **HashSet hashSet=new HashSet(); **调用

public HashSet() {
    map = new HashMap<>();
}

执行 hashSet.add(“jack”);

//2.
public boolean add(E e) { // e:"jack"
    //PRESENT=>占位功能(private static final Object PRESENT = new Object();)
    return map.put(e, PRESENT)==null;//map:"{}" e:"jack"
}

//3.
public V put(K key, V value) { // key:"jack" value:PRESENT
    //hash(key)计算hash值 存放值表中的值
    return putVal(hash(key), key, value, false, true);
}

//4.
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//5. 真正重要的方法
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;
    // 根据key,得到hash 去计算该key应该存放在table表的哪个索引位置,并把这个位置的对象
    // 赋给p
    // 判断p是否等于null,如果等于null,说明该位置还未存放数据
    // 就创建一个Node(key="jack",value=PRESENT)在HashMap中value是自己输入的值
    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指向的节点key是同一对象
        //(2)p指向的Node节点的key的equals()和准备加入的key相比较后市相同的
        //就不可添加
        if (p.hash == hash && // p 直接指向p = tab[i = (n - 1) & 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()对当前的链表进行树化(红黑树)
            //	注意:在树化之前,treeifyBin()方法还需判断MIN_TREEIFY_CAPACITY=64
            //	if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            //		resize();
            //	如果上面条件成立,先对table扩容,
            //	只有上面条件不成立时,才进行转成红黑树
            //(2)依次对该链表比较后,如发现有相同情况,直接break
            for (int binCount = 0; ; ++binCount) {
                //判断时,直接对辅助变量e赋值,让它指向p的下一个结点
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD(8) - 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;//在HashMap中实现一样key 替换旧的value
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
分析HashSet扩容和转红黑树机制
  • HashSet底层时HashMap,第一次添加时,table数组扩容到16,临界值(threshold)时16*加载因子(loadFactor)是0.75=12
  • 如果table数组使用到了临界值12,就会扩容到16*2=32,新的临界值就是32*0.72=24,以此类推
  • 在Java8中,如果一条链表的元素个数到了TREEIEY_THRESHOLD(默认是8),并且table大小>=MIN_TREEIEY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

2022-4-12笔记

  • 在HashSet中,添加元素 时,不一定需要在table上添加table的size才增加。只要有元素添加至table中,不管是在table格子上还是某一格子的链表上,size都增加
HashSet课堂练习1

定义一个Employee类,该类包含:private成员属性name,age

要求:1.创建3个Employee放入HashSet中
2.当name和age的值相同时,认为是相同员工,不能添加到HashSet集合中

package main.java.com.chapter14.set;

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

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/12 15:46
 * @Version 1.0
 */
public class HashSetExercise {
    public static void main(String[] args) {
        Set hashSet = new HashSet();
        hashSet.add(new Employee("jack",20));
        hashSet.add(new Employee("jack",20));
        hashSet.add(new Employee("jack",22));
        hashSet.add(new Employee("tome",22));

        System.out.println(hashSet);
    }
}

class Employee{
    private String name;
    private int age;

    public Employee(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 boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                Objects.equals(name, employee.name);
    }

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

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
HashSet课堂练习2

定义一个Employee类,该英包含:private员属性name,sth,adau)a求:ya一型),其中 birthday 为 MyDate类型(属性包括: year, month, day),

要求:
1.创建3个Employee放入HashSet中
2当name和birthday的值相同时,认为是相同员工,不能添加到HashSet集合中

package main.java.com.chapter14.set;

import java.util.HashSet;
import java.util.Objects;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/12 16:03
 * @Version 1.0
 */
public class HashSetExercise02 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("jack",22,new MyDate(2000,1,1)));//ok
        hashSet.add(new Employee("jack",22,new MyDate(2000,1,1)));//no
        hashSet.add(new Employee("jack",22,new MyDate(2000,2,1)));//ok
        System.out.println(hashSet);
    }
}

class MyDate{
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyDate myDate = (MyDate) o;
        return year == myDate.year &&
                month == myDate.month &&
                day == myDate.day;
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}

class Employee{
    private  String name;
    private  int sal;
    private MyDate birthday;

    public Employee(String name, int sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public int getSal() {
        return sal;
    }

    public void setSal(int sal) {
        this.sal = sal;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

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

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

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }
}

LinkedHashSet的全面说明
  • LinkedHashSet是HashSet的子类
  • LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
  • LInkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是插入顺序保存的
  • LinkedHashSet不允许添加重复元素
    *在这里插入图片描述
LinkedHashSet课后练习题

Car类(属性:name,price),如果name和price-样,则认为是相同元素,就不能添加。

package main.java.com.chapter14.set;

import java.util.LinkedHashSet;
import java.util.Objects;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/12 17:19
 * @Version 1.0
 */
public class LinkedHsahSetExercise {
    public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new Car("三轮车",100));
        linkedHashSet.add(new Car("三轮车",100));
        linkedHashSet.add(new Car("三轮车",10));
        System.out.println(linkedHashSet);
    }
}
class Car{
    private String name;
    private int price;

    public Car(String name, int price) {
        this.name = name;
        this.price = price;
    }

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

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


    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
TreeSet介绍
  • 当我们用无参构造器创建时,仍时无序的

  • TreeSet treeSet = new TreeSet(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            //调用String的比较方法实现从大到小排序
            return ((String)o2).compareTo((String)o1);
        }
    });
    
  • 其内部排序的方式有compar()方法决定

Map接口和常用方法

Map接口特点

  • Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
  • Map中key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
  • Map中的key不可重复,原因和HashSet一样
  • Map中value可以重复
  • Map中key可以为null,value也可以为null,注意:key为null,只能有一个,value为null,可以多个(key还是以hash值存储在table中,而value不是以hash值存放至表中,在不同的table位置上可以存在同个value)
  • 常用String类作为Map的key
  • key和value之间在单向一对一关系,即通过指定的key总能找到对应的value
  • k-v最后是HashMap$Node node = newBode(hash,key,value,null)
  • k-v 为方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型是Entry,里面一个Entry对象就有k,v EntrySet<Entry<K,V>>
    *在这里插入图片描述

Map常用方法

2022-4-13学习笔记

  • put(k,v) 添加
  • remove(key) 根据键值删除映射关系
  • get(key) 根据键值获取值
  • size() 获取元素个数
  • isEmpty() 判断是否个数为0
  • clear() 清除
  • containsKey(key) 查找键值是否存在

Map接口遍历方式

  • containsKey() 查找键值
  • keySet() 获取所有的键
  • entrySet() 获取所有的关系
  • values 获取所有的值
代码演示遍历方式
package main.java.com.chapter14.map;

import java.util.*;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/13 15:02
 * @Version 1.0
 */
@SuppressWarnings({"all"})
public class MapFor {
    public static void main(String[] args) {
        Map map=new HashMap();
        map.put("a","b");
        map.put("c","d");
        map.put("e","f");
        map.put("g","h");
        map.put("j","k");

        // 第一种方式
        System.out.println("----使用keySet 加 增强for循环遍历----");
        Set keys = map.keySet();
        for (Object key : keys) {
            System.out.println(key + "-" + map.get(key));
        }
        System.out.println("----使用keySet 加 迭代器遍历----");
        Iterator iterator1= keys.iterator();
        while (iterator1.hasNext()) {
            Object key =  iterator1.next();
            System.out.println(key + "-" + map.get(key));
        }

        //第二种方式 只是获取值
        System.out.println("----使用values() Collection 获取到值----");
        Collection values=map.values();
        for (Object value :values) {
            System.out.println(value);
        }

        //第三种方式
        System.out.println("----使用entrySet----");
        Set entrySet = map.entrySet();
        for (Object entry :entrySet) {
            Map.Entry entry1=(Map.Entry) entry;
            System.out.println(entry1.getKey() + "-" + entry1.getValue());
        }

    }
}

Map课堂练习

使用HashMap添加3个员工对象,要求键:员工id
值:员工对象
并遍历显示工资>18000的员工(遍历方式最少两种)员工类:姓名、工资、员工id

package main.java.com.chapter14.map;

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

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/13 15:23
 * @Version 1.0
 */
public class MapExercise {
    public static void main(String[] args) {
        Map map=new HashMap();
        map.put(1,new Employee(1,"jack",1500.00));
        map.put(2,new Employee(2,"tom",18000.00));
        map.put(3,new Employee(3,"lucy",110800.00));

        System.out.println("----第一种遍历方式----");
        Set keySet = map.keySet();
        for (Object key :keySet) {
            Employee employee=(Employee)map.get(key);
            if(employee.getSal()>=18000){
                System.out.println(employee);
            }
        }
        System.out.println("----第二种遍历方式----");

        Set entrySet=map.entrySet();
        for (Object entry:entrySet) {
            Map.Entry entry1=(Map.Entry)entry;
            Employee employee=(Employee)entry1.getValue();
            if(employee.getSal()>=18000){
                System.out.println(employee);
            }
        }


    }
}
class Employee{
    private int id;
    private String name;
    private double sal;

    public Employee(int id, String name, double sal) {
        this.id = id;
        this.name = name;
        this.sal = sal;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sal=" + sal +
                '}';
    }
}

HashMap小结

  • Map接口常用实现类:HashMap,HashTable,Properties
  • HashMap是Map接口使用最频繁的实现类
  • HashMap是以key-value对的方式来存储数据(HashMap$Node)
  • key不可重复添加,但是值可以重复添加,允许使用null键和null值
  • 如果添加相同的key,则会覆盖原来的key-value,等同于修改(key不会替换,value会替换)
  • 与HashSet一样,不保证映射的顺序,因为底层以hash表的方式来存储
  • HashMap没有实现同步,因此线程不安全。方法没有做同步互斥操作,没有synchronized

HashMap底层机制及源码剖析

示意图

在这里插入图片描述

扩容机制(和HashSet相同)
  • HashMap底层维护了Node类型的数组table,默认为null
  • 当创建对象时,将加载因子(loadfactor)初始化为0.75
  • 当添加key-value时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则替换value;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果在添加时,容量不够,则需要扩容
  • 第一次添加,需扩容至16,临界值为12(16*0.75)
  • 以后再扩容,则需要扩容table容量为原来的2倍,临界值变为原来的2倍,即24,依次类推
  • 在Java8中,如果一条链表的元素个数超过TREEIEY_THRESHOLD(默认8),并且table大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

HashTable介绍

  • 存放的元素是键值对:即k-v
  • HashTable的键和值都不能为null,否则会抛出NullPointerException
  • HashTable使用的方法基本和HashMap一致
  • HashTable是线程安全的(synchronized),HashMap是线程不安全的
    • 底层有数组HashTable$Entry[] 初始化大小为11
    • 临界值 threshold=8(11*0.75)
    • 扩容按照自己的扩容机制执行((oldCapacity<<1)+1) 在原先基础✖2+1
  • 简单看下底层结构

HashMap与HashTable对比

版本线程安全(同步)效率允许null键null值
HashMap1.2不安全可以
HashTable1.0安全较低不可以

Properties介绍

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

总结-开发中如何选择集合实现类(记住)

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

  • 先判断存储的数据类型(一组对象[单列]或一组键值对[双])
  • 一组对象:Colllection接口
    • 允许重复:List
      • 增删多:LinkedLIst(底层维护了一个双向链表)
      • 改查多:ArrayList(底层维护了一个Object类型的可变数组)
    • 不允许重复:Set
      • 无序:HashSet(底层是HashMap,维护了一个哈希表,即数组+链表+红黑树
      • 排序:TreeSet
      • 插入和取出顺序一致:LinkedHashSet【底层LinkedHahsMap->HashMap】(底层维护了 数组+双向链表)
  • 一组键值对:Map
    • 键无序:HashMap(底层是哈希表+jdk7(数组+链表) jdk8(数组+链表+红黑树)
    • 键排序:TreeMap
    • 键插入和去除顺序一样:LinkedHashMap
    • 读取文件:Properties

Collections工具类

  • Collections是一个操作Set、List和Map等集合的工具类
  • Collections提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
  • 排序操作
    • reverse(List):反转List种元素的顺序
    • shuffle(List):对List集合元素进行随机排序
    • sort(List):根据元素的自然顺序对指定的List集合元素升序排序
    • sort(List,Comparator):根据指定的Comparator产生的顺序对List集合进行排序
    • swap(List,int,int):对指定List集合的i处元素和j处元素进行交换
  • 查找、替换
    • Object max(Collection):根据元素的自然排序,返回给定集合最大的元素
    • Object max(Collection,Comparator):根据Comparator指定的顺序、返回给定集合种最大元素
    • Object min(Collection):
    • Object min(Collection,Comparator):
    • int frequency(Collection,Object):返回指定集合种指定元素出现的次数
    • void copy(List dest,List src):将src的内容复制到dest中
      • 在拷贝中,dest的长度需要大于或者等于src的长度,否则报出异常
    • boolean replaceAll(List list,Object odlVal,Object newVal):使用新值替换List对象的所有旧值

章节作业

作业1

按要求实现:

(1)封装一个新闻类,包含标题和内容属性,提供get、set方法,重写toString方法,打印对象时只打印标题;

(2)只提供一个带参数的构造器,实例化对象时,只初始化标题;并且实例化两个对象:新闻一:新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧新闻二:男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生

(3)将新闻对象添加到ArrayList集合中,并且进行倒序遍历;

(4)在遍历集合过程中,对新闻标题进行处理,超过15字的只保留前15个,然后在后边加“.…."

(5)在控制台打印遍历出经过处理的新闻标题;

代码

package main.java.com.chapter14.homeWork;

import java.util.ArrayList;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/14 16:53
 * @Version 1.0
 */
public class homeWork01 {
    public static void main(String[] args) {
        news news_1 = new news("新冠确诊病例超千万,数百万印度教信徒赴恒河'\"'圣浴'\"'引民众担忧");
        news news_2 = new news("突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生");

        ArrayList arrayList = new ArrayList();
        arrayList.add(news_1);
        arrayList.add(news_2);

        for(int i=arrayList.size()-1;i>=0;i--){
            news tem=(news)arrayList.get(i);
            String title=processTitle(tem.getTitle());
            tem.setTitle(title);
            System.out.println(tem);
        }
    }

    public static String processTitle(String title){
        if(title==null){
            return "";
        }
        if(title.length()>15){
            return title.substring(0,15)+"....";
        }else{
            return title;
        }
    }
}

class news{
    private String title;
    private String content;

    public news(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

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

作业2

使用ArrayList完成对对象Car {name, price}的各种操作

1.add:添加单个元素
2.remove:删除指定元素Car car = new Car(“宝马”,400000);
3.contains:查找元素是否存在Car car2 = new Car(“宾利”,5000000);
4.size:获取元素个数
5.isEmpty:判断是否为空
6.clear:清空
7.addAll:添加多个元素
8.containsAll:查找多个元素是否都存在9.removeAll:删除多个元素 使用增强for和迭代器来遍历所有的car,需要重写Car 的toString方法

代码

package main.java.com.chapter14.homeWork;

import java.util.ArrayList;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/14 17:09
 * @Version 1.0
 */
public class homeWork02 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();

        arrayList.add(new Car("宝马",500000.0));
        arrayList.add(new Car("奥迪",502000.0));
        arrayList.add(new Car("凯迪拉克",400000.0));

        Car baoma = new Car("宝马",500000.0);
        Car binli = new Car("宾利",500000.0);
        arrayList.remove(baoma);
        Boolean binli_contains=arrayList.contains(binli);
        
    }
}

class Car{
    private String name;
    private double price;

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

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

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

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

作业3

按要求完成下列任务
1)使用HashMap类实例化一个Map类型的对象m,键(String)和值(int)分别用于存储员工的姓名和工资,存入数据如下:jack—650元; tom-1200元;smith-
2900元;
2)将jack的工资更改为2600元

3)为所有员工工资加薪100元;

4)遍历集合中所有的员工

5)遍历集合中所有的工资

代码

package main.java.com.chapter14.homeWork;

import java.util.*;

/**
 * @Author BaoYuan
 * @CreateDate 2022/4/14 17:15
 * @Version 1.0
 */
public class homeWork {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put("jack",650);
        hashMap.put("tom",12000);
        hashMap.put("smith",2900);

        System.out.println(hashMap);

        hashMap.replace("jack",2600);

        //加薪
        Set keySet=hashMap.keySet();
        for (Object key :keySet) {
            hashMap.replace(key,(int)hashMap.get(key)+100);
        }

        //遍历
        Set entrySet = hashMap.entrySet();
        for (Object entry :entrySet) {
            Map.Entry entry1=(Map.Entry)entry;
            System.out.print(entry1.getKey()+"-"+entry1.getValue());
        }
        System.out.println();
        System.out.println(hashMap);

        //遍历工资
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry =  (Map.Entry)iterator.next();
            System.out.println("工资:"+entry.getValue());
        }

        Collection values = hashMap.values();
        for (Object value :values) {
            System.out.println("工资"+value);
        }

    }
}

class employee{
    private String name;
    private int sal;

    public employee(String name, int sal) {
        this.name = name;
        this.sal = sal;
    }

    public String getName() {
        return name;
    }

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

    public int getSal() {
        return sal;
    }

    public void setSal(int sal) {
        this.sal = sal;
    }

    @Override
    public String toString() {
        return "employee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                '}';
    }
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值