Collection接口和常用方法及案例分析

Collection接口遍历对象方式1-迭代器基本介绍

  • Iterator对象成为迭代器,主要是用于遍历Collection集合 中的元素
  • 所以实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,既可以返回一个迭代器。
  • Iterator 仅用于遍历集合,Iterator 本身并不存放对象。
package collection_;

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

public class CollectionIterator {
    public static void main(String[] args) {
        ArrayList col = new ArrayList();
        col.add(new Book("三国演义","罗贯中",12));
        col.add(new Book("小李飞刀","古龙",13));
        col.add(new Book("红楼梦","曹雪芹",30));
//        System.out.println("col="+ col);
        //1、先得到 col 对应的迭代器
        Iterator iterator = col.iterator();
        //2、使用while 循环遍历
        while (iterator .hasNext()){    //判断是否还有数据
            //返回下一个元素,类型是Object
            Object obj = iterator .next();
            System.out.println("obj=" + obj);
        }
        //快速生成while快捷键 itit
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            
        }
        //3.当退出while循环后,这时iterator迭代器,指向最后的元素
        iterator.next();  //NoSuchElementException
        //4、如果希望再次遍历,需要重置我们的迭代器
        //第二次遍历
        iterator = col.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
        }
    }
}

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

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

  • 基本语法

    for(元素类型 元素名 : 集合名或数组名) {
        System.out.println(元素名);
    }
    

List接口

List基本介绍
  1. List 接口是Colleciton接口的子接口
  2. LIs集合中的每个元素都有其对应的顺序索引,即支持索引。
  3. List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
  4. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
List接口习题练习
package list_;

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

//添加10个以上的元素(比如String "hello"),在2号位插入一个元素"比比东"
//获得第五个元素,删除第六个元素,修改第七个元素、在使用迭代器遍历集合,要求:使用List的实现类ArrayList完成
public class ListExercise {
    public static void main(String[] args) {
        List list = new ArrayList();
        for(int i=0;i<12;i++){
            list.add("hello "+ i);
        }
        list.add(1,"比比东");
        System.out.println("第五个元素=" + list.get(4));
        list.remove(5);
        list.set(6,"三国演义");
        Iterator col=list.iterator();
        while (col.hasNext()) {
            Object obj =  col.next();
            System.out.println("obj" + obj);
        }
    }
}

List、ArrayList、LinkedList、Vecctor接口遍历方式

package list_;

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

public class ListFor {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("鱼香肉丝");
        list.add("marry");
        //迭代器
/*        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object obj =  iterator.next();
            System.out.println("obj= " + obj);
        }*/
        //增强for
/*        for( Object obj : list){
            System.out.println("obj=" + obj);
        }*/
        //普通for
        for(int i=0;i< list.size();i++){
            System.out.println("list= "+ list.get(i));
        }
    }
}
List 排序练习
package list_;

import java.util.ArrayList;
import java.util.List;
//使用List的实现类添加图示并格式化输出
//按照价格从小到大,使用ArrayList LinkedList Vector三种集合实现
public class ListExcise02 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Book("书001", "作者01", 99));
        list.add(new Book("书002", "作者02", 15));
        list.add(new Book("书003", "作者03", 45));
        list.add(new Book("书004", "作者04", 8));
        list.add(new Book("书005", "作者05", 80));

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


    //静态方法
    //价格要求时从小到打
    public static void bubble(List list){
        Book temp;
        for(int i=0;i< list.size()-1;i++){
            for (int j = 0; j < list.size()-1-i; 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 "名称:" + name+ "\t\t价格:"+ price + "\t\t作者:"+ author;
    }
}

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

ArraysList的注意事项

permits all elements, including null, ArrayList 可以加入null, 并且多个

ArrayList 是由数组来实现数据存储的。

ArrayList 基本等同于Vector, 除了 ArrayList 是线程不安全(执行效率高) 看源码。

在多线程情况下,不建议使用ArrayList

ArrayList分析

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

当创建ArrayList对象时,如果使用的时无参构造器,则初始化elementData 容量为0, 第1次添加,则扩容elementData 为10,如果需要再次扩容,则扩容elementData 为1.5倍。

如果使用的是指定大小的构造器,则初始elementData 容量为指定大小,如果需要扩容,则直接扩容elementData 为1.5倍。

Vertor的基本介绍

Verctor 类的定义说明
public class Vection<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
  • Vector 底层也是一个对象数组, protected Object[] elementData;

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

    public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        return elementData(index);
    }
    
  • 在开发中,需要线程同步安全时,考虑使用Vector

LinkedList的全面说明

  • LinkedList实现了底层双向链表和双端队列特点
  • 可以添加任意元素(元素可以重复),包括null
  • 线程不安全,没有实现同步
LinkedList的底层操作机制
  • LinkedList 底层维护了一个双向链表
  • LinkedList 中维护两个属性first 和 last分别指向 首节点和未节点
  • 每个节点(Node对象),里面又维护来了prev、next、item、三个属性,其中通过prev指向了前一个,通过next指向后一个结点,最终实现双向链表。
  • 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
  • 模拟一个简单的双向链表
package list_;

public class LinkedList01 {
    public static void main(String[] args) {
        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node marry = new Node("marry");
        //连接三个结点,形成双向链表
        // jack -> tom -> marry
        // marry -> tom -> jack
        jack.next = tom;
        tom.next = marry;
        marry.pre = tom;
        tom.pre = jack;

        Node first = jack;  // 让first 引用指向 jack,就是双向链表的头结点
        Node last = marry;  // 让last 引用指向marry,就是 双向链表的尾结点

        // 演示,从头到尾进行遍历
        System.out.println("===从头到尾进行遍历===");
        while (true){
            if(first == null){
                break;
            }
            // 输出first 信息
            System.out.println(first);
            first = first.next;
        }
        last = marry;   //让last 重新指向最后一个结点
        //演示,从尾到头的遍历
        System.out.println("===从尾到头的遍历===");
        while(true){
            if(last == null){
                break;
            }
            // 输出last
            System.out.println(last);
            last = last.pre;
        }
        // 演示链表的添加对象/数据,是多么的方便
        // 要求, 是在 tom ------- marry 之间,插入一个对象 smith
        Node smith = new Node("smith");

        smith.next = marry;
        smith.pre = tom;
        marry.pre = smith ;
        tom.next = smith;
        // 让first 再次指向Jack
        first = jack;

        System.out.println("===修改后遍历===");

        while (true) {
            if(first == null) {
                break;
            }
            System.out.println(first);
            first = first .next;
        }
    }
}
//定义一个Node 类,Node 对象 表示双向链表的一个点结点
class Node{
    public Object item;     //真正存放数据的地方
    public Node next;      //指向下一个结点
    public Node pre;       //指向上一个结点
    public Node(Object name) {
        this.item = name;
    }
    public String toString() {
        return "Node name=" + item;
    }
}
ArrayList 和 LinkedList 比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJEKP50g-1663041733752)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20220903182919583.png)]

如何选择ArrayList 和 LinkedList:

  • 如果我们改查的操作多,多选择ArrayList
  • 如果我们增删的操作多,选择LinkedList
  • 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
  • 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList,根据业务合理选择

Set 接口

Set 接口基本介绍
  1. 无序(添加和取出的顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个null
  3. JDK API 中Set 接口的实现类有 HashSet、TreeSet
@SuppressWarnings({"all"})
public class SetMethod {
    public static void main(String[] args) {
        //1.以Set 接口的实现类 HashSet 来讲解 Set 接口的方法
        //2.set 接口的实现类的对象(Set接口对象),不能存放重复元素
        //3.set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
        //4.注意:取出的顺序的顺序虽然不是添加的顺序,但是它取出顺序是固定的

        Set set = new HashSet();
        set.add("john");
        set.add("lucy");
        set.add("john");
        set.add("jack");
        set.add(null);
        set.add(null);
        System.out.println("set= "+ set);
        // 遍历  1、迭代器
        System.out.println("=====使用迭代器=====");
        Iterator col= set.iterator();
        while (col.hasNext()) {
            Object obj =  col.next();
            System.out.println("obj=" + obj);
        }
        // 遍历 2、增强for
        System.out.println("===增强for循环===");
        for (Object o : set) {
            System.out.println("o=" + o);
        }
        // set 接口对象,不能通过索引来获取
    }
}
Set 接口实现类- HashSet
  • HashSet 实现了Set 接口
  • HashSet 实际上是Hashmap ,看下源码。
public HashSet() {
    map = new HashMap<>();
}
  • 可以存放null 值,但是只能有一个null
  • HashSet 不保证元素是有序的,取决于hash 后,在确定索引的结果。(即,不保证存放元素的顺序和取出顺序一致)
  • 不能有重复元素/对象,在前面Set 接口使用已经讲过
public class HashSet_ {
    public static void main(String[] args) {
        // HashSet 可以存放null ,但是只能有一个null ,即元素不能重复
        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 = new HashSet();
        System.out.println("set=" +set);
        // Hashset 不能添加相同的元素/ 数据?
        set.add("lucy");   //添加成功
        set.add("lucy");   //加入不了
        set.add(new Dog("tom"));//OK
        set.add(new Dog("tom"));//OK
        System.out.println("set="+ set);
        // 再加深以下,非常经典的面试题
        // 分析源码,即add 发生了什么?
        set.add(new String("PPT")); //ok
        set.add(new String("PPT")); //?  加入不了
        System.out.println("set=" + set);
    }
}
class Dog {
    private String name;
    public Dog(String name){
        this.name=name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}
模拟一个HashSet 的底层 (HashMap 的底层结构),数组+链表模拟
@SuppressWarnings({"all"})
public class HashSetStructure {
    public static void main(String[] args) {
        // 模拟一个HashSet 的底层 (HashMap 的底层结构)
        // 1、创建一个数组,数组的类型是 Node[]
        Node[] table = new Node[16];
        System.out.println("table=" + table);
        // 2、创建结点
        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;
    }
}
HashSet底层结构,添加元素
  1. 添加一个元素时,先得到hash值(hashCode方法) ,对哈希值进行运算,得出的值会转换成-> 索引值
  2. 找到存储数据表table ,看这个索引位置是否已经存放的有元素
  3. 如果没有,直接加入
  4. 如果有,调用equals 比较,如果相同,就放弃添加,如果不相同,则添加到最后
  5. 在Java 8中,如果一条链表的元素个数(包括8)超过 TREEIFY_THRESHOLD(默认是8),并且table 的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
  • 源码解读……
HashSet练习

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A1d9FkV1-1663041733753)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20220909170106980.png)]

  • 如果name 和 age 的值相同,在使用equals 时,返回true

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBqKGp44-1663041733754)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20220909170447618.png)]

  • 如果name 和 age的值相同,在计算hashCode( )时,返回相同的结果
public class HashSetExercise {
    public static void main(String[] args) {
/*      定义一个Employee类,该类包含:private 成员 属性name,age 要求:
        1、创建3个Employee对象 放入 HashSet中
        2、当 name 和age的值相同是,认为是相同员工,不能添加到HashSet 集合中*/
        HashSet hashSet= new HashSet();
        hashSet.add(new Employee("Tom",19));
        hashSet.add(new Employee("jack",22));
        hashSet.add(new Employee("Tom",19));
        System.out.println("hashSet="+ 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 String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    // 如果name 和 age 值相同,则返回相同的hash值

    @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);
    }
}
Set接口实现类-LinkedHashSet
  • LinkedHashSet 是 HashSet 的子类
  • LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
  • LinkedHashSet 不允许添加重复元素
  • LinkedHastSet 中维护了一个hash表和双向链表(LinkedHashSet 有 head 和 tail )
  • 每一个节点有before 和after 属性,这样可以形成双向链表
  • 在添加一个元素时,先求hash 的值,再求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表中(如果已经存在,不添加[原则和hashset一样])
tail.next = newElement // 示意代码
newElement.pre = tail;
tail = newElement;
  • 这样的话,我们遍历Linked HashSet 也能确保插入顺序和遍历顺序一致
  1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致
  2. LinkedHashSet 底层维护的是一个 linkedhaseMap(是HashMap 的子类)
  3. LinkedHashSet 底层结构 (数组+双向链表)
  4. 添加第一次时,直接将 数组 table 扩容 到16,存放的节点类型是 LinkedHashMap$Entry
  5. 数组是 HashMap N o d e [ ] 存放的元素 / 数据是 L i n k e d H a s h M a p Node[] 存放的元素/数据是 LinkedHashMap Node[]存放的元素/数据是LinkedHashMapEntry类型
LinkedHashSet练习
package set_;
import java.util.LinkedHashSet;
import java.util.Objects;

/**
 * @author 马凯
 * @version 1.0
 */
// Car 类(属性:name,price),如果name 和price 一样,则认为是相同元素,就不能添加。
    @SuppressWarnings({"all"})
public class LinkedHashSetExercise {
    public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new Car("奥拓",1000));
        linkedHashSet.add(new Car("奥迪",300000));
        linkedHashSet.add(new Car("法拉利",1000000));
        linkedHashSet.add(new Car("奥迪",300000));
        linkedHashSet.add(new Car("保时捷",70000000));
        linkedHashSet.add(new Car("奥迪",300000));

        System.out.println("linkedHashSet=" +linkedHashSet);

    }
}
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 "\nCar{" +
                "name='" + name + '\'' +
                ", 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 Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
    }

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

Map接口

  • Map接口实现类的特点[很实用] 注:此处为 JDK 8的Map接口的特点
  1. Map与Collection 并列存在。用于保存具有映射关系的数据:Key-Value
  2. Map 中的Key 和Value 可以是任何引用类型的数据,会封装到Hash Map$Node对象中
  3. Map 中的Key 不允许重复,原因和 HashSet 一样
  4. Map 中的value可以重复
  5. Map 中value 为null ,value 也可以为null,注意key 为 null,只能有一个,value 为null,可以有多个
  6. 常用String 类作为Map 的key
  7. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的value
  8. Map 存放数据的key-value 示意图,一对 k-v 是放在一个HashMap$Node 中的,有因为Node实现了Entry 接口,有些书上也说,一对k-v 就是一个Entry
Map接口和常用方法
  1. put :添加
  2. remove:根据键删除映射关系
  3. get:根据键获取值
  4. size:获取元素个数
  5. isEmpty:判断个数是否为0
  6. clear:清楚
  7. containsKey:查找键是否存在
boolean containsKey = map.containsKey(null);
System.out.println(containsKey);
System.out.println(map.containsValue("Tom"));
Map接口遍历方法
  1. containsKey:查找键是否存在
  2. keySet:获取所有的键
  3. entrySet:获取所有关系
  4. values:获取所有的值
package map_;

import java.util.*;

/**
 * @author 马凯
 * @version 1.0
 */
@SuppressWarnings({"all"})
public class MapFor {
    public static void main(String[] args) {
        // 演示map 接口常用方法
        Map map = new HashMap();
        map.put("01","Tom");
        map.put("02","Mike");
        map.put("03","Mike");
        map.put("04",null);
        map.put(null,"Jack");
        map.put("05","Smith");
        // 第一组:先取出所有的Key,通过Key 取出对应的Value
        Set keyset = map.keySet();
        // 1.增强for
        System.out.println("======第一种方式=======");
        for (Object key : keyset) {
            System.out.println(key + "-" + map.get(key) );
        }
        // 2.迭代器
        System.out.println("======第二种方式========");
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(key + "-" + map.get(key) );
        }
        // 第二组:把所有的values取出
        Collection values = map.values();
        //        for (Object value : values) {
        //            System.out.println(value);
        //        }
        System.out.println("---取出所有的value 迭代器");
        Iterator iterator2= values.iterator();
        while(iterator2.hasNext()){
            Object value = iterator2.next();
            System.out.println(value);
        }
        // 第三组:通过EntrySet 来获取 k-v
        Set entryset = map.entrySet();  // EntrySet<Map.Entry<k-v>>
        // (1) 增强for
        System.out.println("-----使用EntrySet 的 for 增强-------");
        for(Object entry : entryset) {
            //将entry(没转之前为Object) 转成 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        //(2) 迭代器
        Iterator iterator3 = entryset.iterator();
        while(iterator3.hasNext()){
            Object entry = iterator3.next();
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey()+"- "+ m.getValue());
        }
    }
}
Map 习题练习
package map_;

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

// 使用HashMap 添加3个员工对象,要求,键:员工id,值:员工对象
// 并遍历显示工资>18000 的员工(遍历方式最少两种) 员工类:姓名、工资、员工id
    @SuppressWarnings({"all"})
public class MapExercise01 {
    public static void main(String[] args) {
        Map hashMap = new HashMap();
        hashMap.put(1,new Employee("Tom",1,18001));
        hashMap.put(2,new Employee("jack",2,20000));
        hashMap.put(3,new Employee("Marry",3,9000));
        // 1.使用keyset -> 增强for遍历
        Set keySet = hashMap.keySet();
        for(Object key : keySet){
            // 先获取value
            Employee employee =(Employee) hashMap.get(key);
            if(employee.getWages()>18000){
                System.out.println(employee);
            }
        }
        // 2.使用EntrySet -> 迭代器
        System.out.println("=====迭代器======");
        Set entrySet = hashMap.entrySet();
        Iterator iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Map.Entry entry =(Map.Entry) iterator.next();
            Employee employee =(Employee) entry.getValue();
            if(employee.getWages()>18000){
                System.out.println(employee);
            }
        }
    }
}
class Employee{
    private String name;
    private int id;
    private int wages;

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

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

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

    public int getWages() {
        return wages;
    }

    public void setWages(int wages) {
        this.wages = wages;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", wages=" + wages +
                '}';
    }
}
HashMap 小结
  1. Map接口的常用实现类:HashMap、Hashtable和Properties.
  2. Hashmap 是Map 接口使用频率最高的实现类。
  3. HashMap 是以 key-val 对的方式来存储数据(HashMap$Node类型)
  4. key 不能重复,但是是值可以重复,允许使用null 键和null 值。
  5. 如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key 不会替换,val会替换)
  6. 与HashSet一样,不保证映射的顺序,因为底层是以hash 表的方式来存储的
  7. Hash Map没有实现同步,因此线程是不安全的。方法没有做同步互斥的操作,没有synchronized
  • HashMap 底层机制及源码剖析
  1. HashMap diceng维护了Node类型的数组table,默认为null
  2. 当创建对象时,将加载因子(loadfactor)初始化为0.75
  3. 当添加key-val时,通过key 的哈希值得到在table 的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接提花value,如果不相等需要判断是树结构还是链表结构,做出相应处理,如果添加时发现容量不够,则需要扩容。
  4. 第一次添加,则需要扩容table容量为16,临界值(threshold) 为12.
  5. 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,以此类推。
  6. 在 java 8中,如果一条链表的元素个数超过 TREEIFT_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
HashMap 源码分析
package map_;

import java.util.HashMap;

@SuppressWarnings({"all"})
public class HashMapSource1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java",10);
        map.put("php",10);
        map.put("java",20);  // 替换value

        System.out.println("map= " + map);
        /* HashMap源码解读
        1.执行构造器 new HashMap()
         初始化加载因子 loadfactor = 0.75
         HashMap$Node[] table = null
        2.执行put 调用 hash方法,计算 key 的hash 值 (h= key.hash.hashCode()()) ^ (h >>> 16)
          public V put(K key, V value) {
                return putVal(hash(key), key, value, false, true);
            }
        3.执行putValue
            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相同,并equals返回真
                     // 并满足(table现有的结点的key和准备添加的key是同一个对象 || equals 返回真))
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    else if (p instanceof TreeNode)//如果当前的table 的已有的Node 是红黑树,就按照红黑树方式处理
                        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个,到8个,后调用 treeifyBin 方法进行红黑树的转换
                                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;
        }
        */
    }
}
HashTable的基本介绍
  1. 存放的元素是键值对:即 k-v
  2. hashtable 的键和值都不能为null,否则会抛出NullPointerException异常
  3. hashtable 使用方法基本上和HashMap一样
  4. HashTable 是线程安全的(synchronized),hashMap 是线程不安全的
  • Map接口实现类-Properties
  1. Properties类继承子Hashtable类并实现了Map接口,也是使用一种键值对的形式来保存数据。
  2. 它的使用特点和Hash table类似
  3. Properties 可以用于从 xxx.properties 文件中,加载数据到Properties类对象,并进行读取和修改
  4. 说明:工作后 xxx.properties 文件通常作为配置文件。
TreeSet的基本介绍
  • TreeMap和TreeSet基本类似

    package set_;
    
    import java.util.Comparator;
    import java.util.TreeSet;
    
    @SuppressWarnings({"all"})
    public class TreeSet_ {
        public static void main(String[] args) {
            //1.当我们使用无参构造器,创建TreeSet时,仍然是无序的
            //2.使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
            TreeSet treeSet = new TreeSet(new Comparator() {
                @Override
                public int compare(Object o1, Object o2) {
                    return ((String)o1).compareTo(((String)o2));
    //                int a=((String)o2).length();
    //                int b=((String)o1).length();
    //                return a-b;
                }
            });
    //        TreeSet treeSet = new TreeSet();
            // 添加数据。
            treeSet.add("jack");
            treeSet.add("tom");
            treeSet.add("Marry");
            treeSet.add("Mi");
            System.out.println("treeSet=" + treeSet);
        }
    }
    
  • Collections 工具类

  1. Collections 是一个操作Set、List和 Map 等集合的工具类
  2. Collections 中提供了一系列静态的方法对集合元素进行排序,查询和修改等操作
  3. 排序操作
reverse(List):反转List 中元素的顺序
shuffle(List):List集合元素进行随机排序
sort(List,Comparator):根据指定的Comparator 产生的顺序对List设计和元素进行排序
swap(List,int ,int):将指定List集合中的i处元素和j处元素进行交换
Object max(Collection):根据元素的自然排序,返回给定集合中最大元素
Object max(CollectionComparator):根据 Comparator 指定的顺序, 返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(CollectionObject):返回指定集合中指定元素的出现次 数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list, Object oldVal, Object newVal): 使用 新值替换 List 对象的所有旧值

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

  • 在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
  1. 先判断存储的类型(一组对象[单列]或一组键值对[双列])

  2. 一组对象[单列]:Collection 接口

    允许重复:list
        增删多:LinkedList [底层维护了一个双向链表]
        改查多:ArrayList  [底层维护了一个Object 类型的可变数组]
    不允许重复:Set
        无序:HashSet [底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)]
        排序:TreeSet 
        插入和取出顺序一致:LinkedHashSet ,维护数组+双向链表
    
  3. 一组键值对[双列]:Map

    	键无序:HashMap [底层是:哈希表  jdk8: 数组+链表+红黑树]
        键排序:TreeMap
        键插入和取出顺序一致:linkedHashMap
        读取文件: Properties
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值