韩顺平零基础30天学会Java【章12 集合】

P499~P553

集合

引出问题

之前保存数据使用的是数组,那么数组有不足的地方,我们分析一下

  1. 长度开始时必须指定,而且一旦指定,不能更改,int[] n =new int[3];
  2. 保存的必须为同一类型的元素
  3. 使用数组进行增加元素
Person[] pers = new Person[1];
per[0]=new Person();

集合

  1. 可以动态保存任意多个对象,使用比较方便
  2. 提供了一系列方便的操作对象的方法:add、remove、set、get

集合的分类

集合主要分两组(单列集合,双列集合)

单列集合List、Set

Collection 接口有两个重要子接口List、Set,他们的实现子类都是单列集合

  • Collection
    • Set
      • TreeSet
      • HashSet
      • SortedSet
    • List
      • Vector
      • ArrayList
      • LinkedList

双列集合Map

Map接口的实现子类是双列集合,存放的是key-value

  • Map
    • Hashtable
      • Properties
    • HashMap
      • LinkedHashMap
    • TreeMap

Collection接口

Collection接口继承Iterable接口

public interface Collection<E> extends Iterable<E>
  • Collection实现子类可以存放多个元素,每个元素可以是Object
  • 有些Collection的实现类,可以存放重复的元素,有些不可以
  • 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
  • Collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的

Collection接口常用方法,Collection01.java

接口不能实例化,以ArrayList子类来演示,Set和List都可以用

  1. add
  2. remove
  3. contains,查找某元素是否存在
  4. size
  5. isEmpty,是否为空
  6. clear,清空
  7. addAll
  8. containsAll,查找多个元素是否都存在
  9. removeAll
import java.util.ArrayList;
import java.util.List;

public class Collection01 {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        //add
        List list = new ArrayList();
        list.add("jack");
        list.add(10); //list.add(new Integer(10))
        list.add(true);
        System.out.println(list);//[jack, 10, true]
        //remove
        list.remove(0);//删除第一个元素
        System.out.println(list);//[10, true]
        list.remove("jack");//删除指定元素
        System.out.println(list);//[10, true]
        //contains//是否含有某元素
        System.out.println(list.contains("jack"));//false
        //size
        System.out.println(list.size());//2
        //isEmpty
        System.out.println(list.isEmpty());//false
        //clear
        list.clear();
        System.out.println(list);//[]
        //addAll
        ArrayList list2 = new ArrayList();
        list2.add("mary");
        list2.add(20);
        list.addAll(list2);
        System.out.println(list);//[mary,20]
        //containsAll
        System.out.println(list.containsAll(list2));//true
        //removeAll
        list.removeAll(list2);
        System.out.println(list);//[]
    }
}

Collection接口遍历元素方法

  1. 使用Iterator迭代器,Iterator01.java
    1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
    2. 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
    3. Iterator仅用于遍历集合,本身并不存放对象
    4. while迭代快捷键:itit
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Iterator01 {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Book("三国演义", "罗贯中", 10.1));
        arrayList.add(new Book("红楼梦", "曹雪芹", 5.1));
        arrayList.add(new Book("西游记", "吴承恩", 15.1));

        Iterator iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();//返回一个Object类型的元素
            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;
    }
}
  1. for循环增强,CollectionFor01.java

    简化版的迭代器,快捷键大写i

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

import java.util.ArrayList;

public class CollectionFor01 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Book("三国演义", "罗贯中", 10.1));
        arrayList.add(new Book("红楼梦", "曹雪芹", 5.1));
        arrayList.add(new Book("西游记", "吴承恩", 15.1));
        for (Object book : arrayList) {
            System.out.println(book);
        }
    }
}

  1. 普通for循环
for(int i=0;i<list.size(),i++){
  Object o=list.get(i);
 }

Iterator迭代器方法

  1. hasNext()
  2. next(),调用next方法之前要用hasNext进行检测。若不调用,且下一条记录无效,直接调用next会抛出NoSuchElementException异常
  3. remove()

练习,CollectionExercise.java

  1. 创建3个Dog{name,age}对象,放入ArrayList中,赋给List引用
  2. 用迭代器和增强for循环两种方式来遍历
  3. 重写Dog的toString方法,输出name和age
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionExercise {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Dog12("d1", 10));
        list.add(new Dog12("d2", 20));
        list.add(new Dog12("d3", 30));
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }
        for (Object o : list) {
            System.out.println(o);
        }
    }
}
class Dog12 {
    private String name;
    private int age;
    public Dog12(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Dog{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

List接口

List接口是collection接口的子接口

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

List接口常用方法

  1. void add(int index, E element);在index位置加入元素
  2. boolean addAll(int index, Collection c);从index位置开始加入c中元素
  3. Object get(int index);获取index位置元素
  4. int indexOf(Object o);返回o首次出现位置
  5. int lastIndexOf(Object o);返回o最后出现位置
  6. Object remove(int index);移除index位置元素并返回
  7. Object set(int index, E element);替换index位置的元素,返回被替换的元素
  8. List subList(int fromIndex, int toIndex);返回fromIndex到toIndex的子集

List练习,ListExercise.java

使用List的实现类添加三本图书,并遍历,打印如下

名称:xx 价格:xx作者:xx

名称:xx 价格:xx作者:xx

名称:xx 价格:xx作者:xx

要求:按价格排序,从低到高

要求使用ArrayList、LinkedList、Vector

结论:只要实现了List接口,那么List的实现类都可以使用List接口中的方法

import java.util.*;

@SuppressWarnings("all")
public class ListExercise {
    public static void sort(List list) {
        int size = list.size();
        for (int i = 0; i < size - 1; i++) {
            for (int j = 0; j < size - 1; j++) {
                Book1 booki = (Book1) list.get(j);
                Book1 bookj = (Book1) list.get(j + 1);
                if (booki.getPrice() > bookj.getPrice()) {
                    list.set(j, bookj);
                    list.set(j + 1, booki);
                }
            }
        }
    }

    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        LinkedList linkedList = new LinkedList();
        Vector vector = new Vector();

        arrayList.add(new Book1("三国", "罗贯中", 10.1));
        arrayList.add(new Book1("三国2", "罗贯中", 220.1));
        arrayList.add(new Book1("三国3", "罗贯中", 100.1));
        arrayList.add(new Book1("红楼梦", "曹雪芹", 5.1));
        arrayList.add(new Book1("西游记", "吴承恩", 15.1));

        sort(arrayList);
        Iterator iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next);
        }
        linkedList.addAll(arrayList);
        vector.addAll(arrayList);
        
        System.out.println(arrayList);
        System.out.println(linkedList);
        System.out.println(vector);
    }
}
class Book1 {
    private String name;
    private String author;
    private double price;
    public Book1(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public String getAuthor() {
        return author;
    }
    public double getPrice() {
        return price;
    }
    @Override
    public String toString() {
        return "名称:" + name + "\t\t" + "价格:" + price + "\t\t" + "作者:" + author;
    }
}

ArrayList注意事项

  1. permits all elements,including null
  2. ArrayList是由数组实现数据存储的
  3. ArrayList基本等于Vector,但ArrayList是线程不安全,在多线程情况下不建议使用ArrayList

ArrayList底层机制,ArrayListSource.java

  1. ArrayList中维护类一个Object类型的数组elementData

    transient Object[] elementData;表示该属性不会被序列化

  2. 当创建对象时,如果使用的时无参构造器,则初始elementData容量为0

  3. 当添加元素时,先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置

  4. 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍

//无参构造器
public class ArrayListSource {
    public static void main(String[] args) {
//        public ArrayList() {
//            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
//        }
//        创建了一个空的elementData数组
        ArrayList list = new ArrayList();
//        使用for给list添加1-10
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
//add
//        public boolean add(E e) {
//            ensureCapacityInternal(size + 1);  // Increments modCount!!
//            elementData[size++] = e;
//            return true;
//        }
//        1、先确定是否要扩容
//        2、再执行赋值操作
//ensureCapacityInternal
//        private void ensureCapacityInternal(int minCapacity) {
//            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
//        }
//calculateCapacity
//        private static int calculateCapacity(Object[] elementData, int minCapacity) {
//            //默认值DEFAULT_CAPACITY为10
//            //确定是否要扩容,第一次minCapacity为10
//            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//                return Math.max(DEFAULT_CAPACITY, minCapacity);
//            }
//            return minCapacity;
//        }
//ensureExplicitCapacity
//        private void ensureExplicitCapacity(int minCapacity) {
//            //modCount++记录集合被修改的次数,防止多个线程同时修改
//            modCount++;
//            //如果elementData容量不够,进行扩容
//            if (minCapacity - elementData.length > 0)//说明容量不够
//                grow(minCapacity);//需要扩容
//        }
//grow
//        //使用扩容机制来确定要扩容到多大
//        private void grow(int minCapacity) {
//            int oldCapacity = elementData.length;
//            //第一次newCapacity=10,之后为1.5倍
//            //oldCapacity >> 1相当于/2,所以oldCapacity + (oldCapacity >> 1)为1.5
//            int newCapacity = oldCapacity + (oldCapacity >> 1);
//            if (newCapacity - minCapacity < 0)
//                newCapacity = minCapacity;
//            if (newCapacity - MAX_ARRAY_SIZE > 0)
//                newCapacity = hugeCapacity(minCapacity);
//            //扩容使用Arrays.copyOf
//            //为什么用copyOf,会保留原先数据,再增加
//            elementData = Arrays.copyOf(elementData, newCapacity);
//        }
        //使用for给list添加11-15,容量为15
        for (int i = 11; i < 15; i++) {
            list.add(i);
        }
        list.add(100);//容量为22
        list.add(200);
        list.add(null);
    }
}

  1. 如果使用指定容量capacity的构造器,则初始elementData容量为capacity
  2. 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍

Vector底层结构和源码,Vector01.java

和ArrayList扩容机制类似

import java.util.Vector;

@SuppressWarnings("all")
public class Vector01 {
    public static void main(String[] args) {
        Vector vector = new Vector();
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
//        public synchronized boolean add(E e) {
//            modCount++;
//            //确定是否需要扩容ensureCapacityHelper
//            ensureCapacityHelper(elementCount + 1);
//            elementData[elementCount++] = e;
//            return true;
//        }
//        private void ensureCapacityHelper(int minCapacity) {
//            if (minCapacity - elementData.length > 0)
//                grow(minCapacity);
//        }
//        private void grow(int minCapacity) {
//            int oldCapacity = elementData.length;
//            //扩容容量((capacityIncrement > 0) ?capacityIncrement : oldCapacity)
//            //如果capacityIncrement>0,相当于翻2倍
//            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);
//        }

        vector.add(100);
    }
}
public class Vector<E> extends AbstractList<E>
  implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    protected Object[] elementData;
}

  1. Vector底层也是一个对象数组

  2. Vector是线程同步的,即线程安全,方法带有synchronized

    开发中需要线程同步安全时,考虑使用Vector

public synchronized E get(int index) {
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
     return elementData(index);
}

LinkedList,LinkedList01.java

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

双向链表

public class LinkedList01 {
    public static void main(String[] args) {
        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node mary = new Node("mary");

        //链接三个节点,形成双向链表
        jack.next = tom;
        tom.next = mary;
        mary.pre = tom;
        tom.pre = jack;

        Node first = jack;//双向链表的首节点
        Node last = mary;//双向链表的尾结点

        //从头到尾遍历
        while(true) {
            if (first == null) {
                break;
            }
            //输出first
            System.out.println(first);
            first = first.next;
        }
        //从尾到头遍历
        while(true){
            if (last==null){
                break;
            }
            //输出first
            System.out.println(last);
            last = last.next;
        }
    }
}
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;
    }
}
  1. LinkedList底层维护了一个双向链表
  2. LinkedList中维护了两个属性first和last分别指向首节点和尾节点
  3. 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中prev指向前一个,next指向后一个,实现双向链表
  4. 元素的删除和添加效率高

add源码,LinkedList02.java


import java.util.LinkedList;

public class LinkedList02 {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);

//        public boolean add(E e) {
//            linkLast(e);
//            return true;
//        }
//
//        void linkLast(E e) {
//            final LinkedList.Node<E> l = last;
//            final LinkedList.Node<E> newNode = new LinkedList.Node<>(l, e, null);
//            last = newNode;
//            if (l == null)
//                first = newNode;
//            else
//                l.next = newNode;
//            size++;
//            modCount++; v
//        }
//
//        private static class Node<E> {
//            E item;
//            LinkedList.Node<E> next;
//            LinkedList.Node<E> prev;
//
//            Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
//                this.item = element;
//                this.next = next;
//                this.prev = prev;
//            }
//        }

    }
}

ArrayList、Vector、LinkedList比较

底层结构 版本 线程安全效率 扩容倍数
ArrayList 可变数组 jdk1.2 不安全,效率高 1.5倍
Vector 可变数组 jdk1.0 安全,效率不高 2倍
LinkedList 双向链表 线程不安全,增删效率高,改查效率低
  1. 如果改查操作多,选择ArrayList
  2. 如果增删操作多,选择LinkedList

Set

  1. 无序取出的顺序和添加顺序不一致,但是是固定的顺序,没有索引
  2. 不允许重复元素,所以最多包含一个null

Set接口常用方法

同Collection接口

遍历方式

  1. 迭代器
  2. 增强for
  3. 不能使用索引的方式

HashSet

  1. 实现了Set接口
  2. HashSet实际上是HashMap
  3. 可以存放null,但只能有一个null
  4. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果
  5. 不能有重复元素

HashSet底层机制,HashSet01.java

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

  1. 添加一个元素时,先得到hash值,会转成索引值
  2. 找到存储数据表table,看这个索引位置是否有存放的元素
  3. 如果没有,直接加入
  4. 如果有,调用equals比较,如果相同,放弃添加,如果不同,则添加到最后
  5. Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认8),并且table大小≥MIN_TREEIFY_CAPACITY(默认64),就会进行红黑树化
import java.util.*;

@SuppressWarnings("all")
public class HashSet01 {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("aaa");
        hashSet.add("bbb");
        hashSet.add("ccc");
//add
//        public boolean add(E e) {
//            //返回null,代表添加成功
//            return map.put(e, PRESENT)==null;
//        }
//put
//        public V put(K key, V value) {
//            return putVal(hash(key), key, value, false, true);
//        }
//hash
//        static final int hash(Object key) {
//            int h;
//            //无符号右移16位得到hash值
//            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//        }
//putVal
//        final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
//            HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;//辅助变量
//            //table是放node节点的数组,是hashMap的属性
//            //如果table为空,第一次扩容,16个空间,1<<4
//            if ((tab = table) == null || (n = tab.length) == 0)
//                n = (tab = resize()).length;
//            //根据key,得到hash去计算key放到table的哪个位置
//            //并把这个位置的对象赋给p
//            //判断p是否为空,如果为空,表示还没存放数据,创建一个Node,放在该位置
//            if ((p = tab[i = (n - 1) & hash]) == null)
//                tab[i] = newNode(hash, key, value, null);
//            else {
//                HashMap.Node<K,V> e; K k;//辅助变量
//                //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//                //并且满足以下两个条件之一
//                //1、准备加入的key 和 p指向的node节点的key 是同一个对象
//                //2、准备加入的key 用程序员编写的equals()方法比较 p指向的node节点的key
//                //则不能加入
//                if (p.hash == hash &&
//                        ((k = p.key) == key || (key != null && key.equals(k))))
//                    e = p;
//                //判断p是不是一棵红黑树,是的话调用putTreeVal
//                else if (p instanceof HashMap.TreeNode)
//                    e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//                //如果table对应索引位置已经是一个链表,使用for循环比较,
//                //1、依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
//                //   如果链表达到8个节点,table大小<64,就调用treeifyBin将链表进行扩容
//                //   如果链表达到8个节点,table大小>64不满足上述条件,则进行树化
//                //2、如果有相同情况,直接break
//                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;
//            //每加入一个节点,不管是table中还是链表上,size就会加1
//            if (++size > threshold)
//                resize();
//            //HashMap空方法
//            afterNodeInsertion(evict);
//            return null;
//        }

    }
}


HashSet的扩容和转成红黑树机制

  1. HashSet底层是HashMap,第一次添加时table数组扩容到16,临界值threshold(16)*加载因子loadFactor(0.75)=12
  2. 如果table数组使用到了临界值12,就会扩容到162=32,新的临界值就是320.75=24,以此类推
  3. Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认8),并且table大小≥MIN_TREEIFY_CAPACITY(默认64),就会进行红黑树化,否则仍然采用数组扩容机制

LinkedHashSet

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

说明:

  1. LinkedHashSet维护了一个hash表和双向链表
  2. 每个节点都有pre和next
  3. 在添加元素时先求hash值,再求索引,确定该元素在table的位置,然后添加元素到双向链表
  4. 这样能确保插入顺序和遍历顺序一致

Map接口,Map01.java

  1. Map与Collection并列存在,用于保存具有映射关系的操作,key-value
  2. Map中的key和value可以是任意引用类型的数据,会封装到HashMap$Node对象中
  3. Map中的Key不允许重复,value可以重复
  4. key可以为null,但只能有一个,value也可以为null
  5. 常用String作为key
  6. key和value之间存在一对一关系,即通过指定key总能找到对应的value
  7. 两个元素key相同时,value替换
  8. 一对k-v放在一个Node中,因为Node实现了Entry接口
import java.util.*;
public class Map01 {
    public static void main(String[] args) {
        Map map = new HashMap();

        map.put("key","value");
        map.put("1","2");
        System.out.println(map);//{1=2, key=value}
    }
}

内部

  1. key-value最后是HashMap$Node node= newNode(hash,key,value,null)
  2. k-v为了方便程序员的遍历,会创建EntrySet集合,该将元素存放进Entry类,
  3. Entry类:EntrySet<Entry<K,V>>,即transient Set<Map.Entry<K,V>> entrySet
  4. entrySet中,定义的类型是Map.Entry,但实际上存放的还是HashMap$Node,这是因为static class Node<K,V> implements Map.Entry<K,V>
  5. 当把HashMap$Node对象存放到entrySet,就方便我们遍历,因为Map.Entry提供了getSet()获取key的Set和getValue()获取value的Collection

Map接口方法

  1. put,添加
  2. remove,根据key删除映射关系
  3. get,根据key获取值
  4. size,获取元素个数
  5. isEmpty,判断元素个数是否为0
  6. clear,清除
  7. containsKey,查找key是否存在

Map遍历方法,MapFor01.java

import java.util.*;
@SuppressWarnings("all")
public class MapFor01 {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("1","A");
        map.put("3","B");
        map.put("2","C");
        map.put("4","D");
        map.put("5","#");

        //第一组:先取出所有key,通过key取出对应的value 返回此映射中包含的键的 set 视图。
        Set keySet = map.keySet();
        //增强for
        for (Object key :keySet) {
//            System.out.println(key+"-"+map.get(key));
        }
        //迭代器
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
//            System.out.println(key+"-"+map.get(key));
        }
        //第二组:取所有values
        Collection values = map.values();
        //Collection所有三种遍历方式

        //第三组:通过EntrySet来获取 返回此映射中包含的映射关系的 set 视图。
        Set entryset = map.entrySet();
        for (Object entry :entryset) {
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey()+"-"+m.getValue());
        }

        Iterator iterator1 = entryset.iterator();
        while (iterator1.hasNext()) {
            Object entry = iterator1.next();
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey()+"-"+m.getValue());
        }
    }
}


Map练习,MapExercise.java

使用HashMap添加3个员工对象,要求

  • 键:员工id
  • 值:员工对象
  • 并遍历显示工资>8000的员工
  • 员工类:姓名、工资、员工id
import java.util.*;
public class MapExercise {
    public static void main(String[] args) {
        Map hashMap = new HashMap();
        hashMap.put(1,new Employee(1,"张1",5000));
        hashMap.put(2,new Employee(2,"张2",10000));
        hashMap.put(3,new Employee(3,"张3",15000));
        for (Object o :hashMap.keySet()) {
            Employee e = (Employee) hashMap.get(o);
            if (e.salary>8000){
                System.out.println(e.name+" "+e.salary);
            }
        }

        Set entrySet = hashMap.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            Map.Entry entry = (Map.Entry) next;
            Employee e = (Employee)entry.getValue();
            if(e.salary>8000){
                System.out.println(e.name+" "+e.salary);
            }
        }

    }
}
class Employee{
    public int id;
    public String name;
    public double salary;

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

HashMap扩容机制

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

HashTable

  1. 存放k-v
  2. k-v都不能为null
  3. hashTable是线程安全的,HashMap是线程不安全的

Properties

  1. 继承自HashTable类并实现了Map接口,也是使用键值对保存数据
  2. Properties可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改

TreeMap

重写compare方法

@Override
public int compare(Object o1,Object o2){
  return ((String) o1).compareTo((String) o2);
}

总结

开发中如何选择集合实现类

  • 判断存储类型
    1. 一组对象:Collection接口
      1. 允许重复:List
        1. 增删多:LinkedList(底层维护了一个双向链表)
        2. 查改多:ArrayList(底层维护Object类型的可变数组)
      2. 不允许重复:Set
        1. 无序:HashSet(底层是HashMap,维护一个哈希表(数组+链表+红黑树))
        2. 排序:TreeSet,(底层是TreeMap),重写compare方法
        3. 插入和取出顺序一致:LinkedHashSet,维护(数组+双向链表)
    2. 一组键值对:Map接口
      1. 键无序:HashMap(底层是哈希表(数组+链表+红黑树))
      2. 键排序:TreeMap
      3. 键插入和取出顺序一致:LinkedHashMap
      4. 读取文件:Properties

Collections工具类

  1. Collections是一个操作Set、List、Map等集合的工具类
  2. 提供了一系列静态方法对集合元素进行排序、查询和修改操作
    1. 排序方法:
      1. reverse(List):反转list元素
      2. shuffle(List):随机排序list元素
      3. sort(List):升序排序
      4. sort(List,Comparator):根据指定Comparator的顺序对list进行排序
Collections.sort(list,new Comparator(){
  @Override
  public int compare(Object o1,Object o2){
    return ((String)o1).length()-((String)o2).length();
  }
}
    1. swap(List,int i,int j):交换list中i和j的位置
1. 查找、替换方法:
    1. max(Collection):最大值
    2. max(Collection, Comparator):根据Comparator指定顺序返回给定集合中最大值
    3. min(Collection)
    4. min(Collection,Comparator)
    5. frequency(Collection,Object):返回集合中指定元素的出现次数
    6. copy(List dest,List src):将src的内容复制到dest中
    7. replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象所有旧值

章节作业

SetHomework01.java

要求:

  1. 封装一个新闻类,包含标题和内容属性提供get、 set方法,重写toString方法,打印对象时只打印标题;
  2. 只提供一个带参数的构造器,实例化对象时,只初始化标题;并且实例化两个对象:
    新闻一:新冠确诊病例超千万,数百万印度教信徒赴恒河 “圣浴”,引民众担忧。
    新闻二:男子突然想起2个月前钓的鱼还在往兜里,一看赶紧放生。
  3. 将新闻对象添加到Arraylist集合中,并进行倒序遍历
  4. 在遍历集合过程中,对新闻标题进行处理,超过15字的只保留前15个,然后在后边加"…"
  5. 在控制台打印遍历出经过处理的新闻标题;

import java.util.*;

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

    public static void main(String[] args) {
//        1. 封装一个新闻类,包含标题和内容属性提供get、 set方法,重写toString方法,打印对象时只打印标题;
//        2. 只提供一个带参数的构造器,实例化对象时,只初始化标题;并且实例化两个对象:
//        新闻一:新冠确诊病例超千万,数百万印度教信徒赴恒河 “圣浴”,引民众担忧。
//        新闻二:男子突然想起2个月前钓的鱼还在往兜里,一看赶紧放生。
//        3. 将新闻对象添加到Arraylist集合中,并进行倒序遍历
//        4. 在遍历集合过程中,对新闻标题进行处理,超过15字的只保留前15个,然后在后边加"..."
//        5. 在控制台打印遍历出经过处理的新闻标题;
        News news1 = new News("新冠确诊病例超千万,数百万印度教信徒赴恒河 “圣浴”,引民众担忧");
        News news2 = new News("男子突然想起2个月前钓的鱼还在往兜里,一看赶紧放生");
        ArrayList arrayList = new ArrayList();
        arrayList.add(news1);
        arrayList.add(news2);
        for (int i = arrayList.size() - 1; i >= 0; i--) {
            News news = (News) arrayList.get(i);
            System.out.println(processTitle(news.getTitle()));
        }
    }
}

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 "title=" + title;
    }
}

HashSet和TreeSet如何去重

  1. HashSet的去重机制:hashCode()+equals(),底层通过存入对象,进行运算得到一个hash值,通过hash值对应的索引,如果发现table索引所在位置,没有数据,就直接存放,如果有数据,就进行equals遍历比较,如果比较后,不相同就加入,否则不加入。
  2. TreeSet去重机制:如果传入了一个Comparator匿名对象,就使用实现的compare方法去重,如果方法返回0,就认为是相同的数据,就不添加。如果没有传入Comparator则以添加的对象实现的Comparable接口的compareTo去重

猜你喜欢

转载自blog.csdn.net/weixin_65656674/article/details/126434787