Java

day15——2023.8.25

Map

之前的Collection称为集合,使用它可以存放单个的对象,可以快速找到我们想要的元素。

而Map称为映射,它可以存放一对键值数据,一对key:value数据

比如:name:张三,age:20,1001:Student{张三,20,1班}

RangeMap key可以重叠吗_RangeMap key可以重叠吗

Map和Collection的区别

1,Map中的元素,是成对出现的,Map中的键是唯一的,值是可以重复的,可以有null值

2,Collection中的元素,都是单个出现的,Collection的子类,set中元素是唯一的,不能重复,List是可以重复的

Map的功能
1.添加功能

V put(K key , V value)

将指定的值与该映射中的指定键相关联(可选操作)。

2.获取功能

V get(Object key)

返回到指定键所映射的值,或 null如果此映射包含该键的映射。

Set<Map.Entry<K,V>> entrySet()

返回此Map中包含的映射的Set视图。

Set keySet()

返回此Map中包含的键的Set视图。

Collection values()

返回此地图中包含的值的Collection视图

3.判断功能

boolean isEmpty()

如果此Map不包含键值映射,则返回 true 。

boolean containsKey(Object key)

如果此映射包含指定键的映射,则返回 true 。

boolean containsValue(Object value)

如果此地图将一个或多个键映射到指定的值,则返回 true 。

4.删除功能

V remove(Object key)

如果存在(从可选的操作),从该地图中删除一个键的映射。

void clear()

从该地图中删除所有的映射(可选操作)。

5.长度功能

int size()

返回此地图中键值映射的数量。

Map的常用子类 HashMap

RangeMap key可以重叠吗_windows_02


HashMap基于哈希表的实现的Map接口。

此实现提供了所有可选的Map操作,并允许null的值和null键,null键不能重复。线程不安全的,存放数据是无序的

构造方法

HashMap()

构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。

HashMap(int initialCapacity)

构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。

HashMap(int initialCapacity, float loadFactor)

构造一个空的 HashMap具有指定的初始容量和负载因子

public class MapDemo {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap() ; //键值都是String
        //通过put方法,完成map数据的存放
            map.put("one","hello");
            map.put("one","mysql");
            map.put("two","java");
            map.put("three","world");
            map.put("1","oracle");
            map.put(null,"spring"); //可以把null作为键
            map.put(null,"Linux"); //可以把null作为键,相同的键对应的值被覆盖
        System.out.println(map);
        //获取
        System.out.println(map.get("one"));
        System.out.println(map.get("2"));  //获取不存在,返回null
        //判断
        System.out.println(map.containsKey("one")); //true
        System.out.println(map.containsValue("hello"));//false
        //移除
        map.remove("one");
        System.out.println(map);
        //长度
        System.out.println(map.size());
        System.out.println("------------------------");
        //遍历
        //第一种:先获取key,通过key再获取值
        Set<String> set = map.keySet(); //先获取所有key的set集合
        for (String key : set) {
            //遍历set
            System.out.println(key + "----" + map.get(key));
        }

        System.out.println("------------------------");
        Map<Integer, String> map1 = new HashMap() ;//键是Integer,值是String
            map1.put(1,"hello");
            map1.put(2,"java");
            map1.put(3,"oracle");
        System.out.println(map1);

        //分开分别获取key和value
        for (Integer key : map1.keySet()) {
            System.out.println("key:" + key);
        }
        for (String value : map1.values()) {
            System.out.println("value:" + value);
        }
        System.out.println("------------------------");


        Map<String, Object> map2 = new HashMap() ;//键是String,值是对象
            map2.put("name","张三");
            map2.put("age",20);
            map2.put("1001",new Student("李四",22,"1班"));
        System.out.println(map2);

        //使用entrySet()方法来实现遍历
        Set<Map.Entry<String, Object>> entries = map2.entrySet();
        //使用增强for遍历返回的entrySet
        for (Map.Entry<String, Object> entry : entries) {
            System.out.println(entry.getKey() + "--" + entry.getValue());
        }
        System.out.println("------------------------");
        //使用iterator遍历返回的entrySet
        Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Object> entry = iterator.next();
            System.out.println(entry.getKey() + "--" + entry.getValue());
        }
    }
}
HashMap的源码解析

HashMap的底层结构 :jdk1.7 数组+链表 ,jdk1.8 之后 数组+链表+红黑树

数组和链表都可以按照用户的意愿来排列元素的次序,是有序(存取有序),

链表中也有缺点,想要获取某个元素,要访问所有元素,直到找到位置,这是比较耗时的

数组+链表的组合,可以看做是一个新的数据结构:散列表 (hash表)

这个结构在存储的时候,不在意元素的顺序,也能够快速的找到元素。

散列表的原理

将每个对象 计算出一个 数值,这个数值称为散列码(hash码),根据这些散列吗,计算数据保存的位置。

每个散列表,在Java中,也称之为 桶 。

一个桶上,多个对象存储的时候可能会遇到散列码相同的情况,这个是,散列码相同的对象,就得存储到同一个位置上。这种情况称为 :散列冲突(hash冲突)

发生散列冲突后,就需要将存放的数据,进行比较,看看发生冲突的对象是否是同一个对象,如果是同一个对象,将原来的替换,如果不是,将对象添加在桶的链表上

HashMap的基本说明

注释上的说明:

基于哈希表的实现的Map接口,允许null的值和null键,不能保证Map的顺序

HashMap的对象有两个影响其性能的参数,初始容量16和负载因子0.75

初始容量太高和负载因子太低,都是不太好的,会影响性能

当存储的数量达到了16*0.75=12的时候,哈希表开始扩容,扩容2倍

如果要存储很多数据在Map中,可以在初始化的时候,把容量设置的高一些,可以提升效率

HashMap是线程不安全的

属性的说明:

默认的初始容量值是16,必须是2的n次方

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

最高容量是2的30次方

static final int MAXIMUM_CAPACITY = 1 << 30;

默认的负载因子是0.75f

static final float DEFAULT_LOAD_FACTOR = 0.75f;

内部链表结构转成红黑树结构的节点,链表的数量=8个

static final int TREEIFY_THRESHOLD = 8;

当树的结构中数量=6了,退化为链表

static final int UNTREEIFY_THRESHOLD = 6;

链表结构转为红黑树另一个要满足的条件,桶的数量=64

static final int MIN_TREEIFY_CAPACITY = 64;

构造方法
无参构造

public HashMap() {

this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

有参构造

public HashMap(int initialCapacity) {
 this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }
public HashMap(int initialCapacity, float loadFactor) {
 //如果初始值、加载因子小于0或者是非数字,就抛异常
//如果初始值超过了最大范围,就把初始值设为最大值
 this.loadFactor = loadFactor;
 this.threshold = tableSizeFor(initialCapacity);
 }

tableSizeFor(initialCapacity) 用来将传入的数值转成2的n次方后,返回

《HashMap中内容解析》

普通方法
put方法:

1,将key和value传入put方法后,先把key传入hash方法后,完成计算,在传入putVal这个方法

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}

2,key传入hash方法后,将key的hashCode值,和key的hashCode值右移16位后,做的位异或运算

这种计算方式是为了减少hash冲突。

static final int hash(Object key) {

int h;

return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}

3,将key经过hash计算后的值、key本身的值、value值,传入putVal

这个是putVal方法中的一个代码片段,表示存放元素的时候,将数组(hashmap对象)的长度-1之后,和key计算出来的hash值做运算,通过运算结果判断这个数组的下标位置是否存在元素,如果没有元素,就将元素传入新节点,创建后,放到这个数组的下标位置

if ((p = tab[i = (n - 1) & hash]) == null)

tab[i] = newNode(hash, key, value, null);

代码拆分:

i = (n - 1) & hash; //计算出来数组的下标
p = tab[i]; //通过下标取出来的值
if(p == null){ //如果这个数组的下标元素为null,那么将这个下标位置指定元素
tab[i] = newNode(hash, key, value, null);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 boolean evict) {
 Node<K,V>[] tab; Node<K,V> p; int n, i;
 if ((tab = table) == null || (n = tab.length) == 0)
 n = (tab = resize()).length;
 if ((p = tab[i = (n - 1) & hash]) == null)
 tab[i] = newNode(hash, key, value, null);
 else {
 Node<K,V> e; K k;
 if (p.hash == hash &&
 ((k = p.key) == key || (key != null && key.equals(k))))
 e = p;
 else if (p instanceof TreeNode)
 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 else {
 for (int binCount = 0; ; ++binCount) {
 if ((e = p.next) == null) {
 p.next = newNode(hash, key, value, null);
 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 treeifyBin(tab, hash);
 break;
 }
 if (e.hash == hash &&
 ((k = e.key) == key || (key != null && key.equals(k))))
 break;
 p = e;
 }
 }
 if (e != null) { // existing mapping for key
 V oldValue = e.value;
 if (!onlyIfAbsent || oldValue == null)
 e.value = value;
 afterNodeAccess(e);
 return oldValue;
 }
 }
 ++modCount;
 if (++size > threshold)
 resize();
 afterNodeInsertion(evict);
 return null;
 }
HashMap中怎么使用散列表存放元素

1,先判断键值对数组table[i]是否为null或者为空,如果是空,执行reisze()方法进行扩容

2, 根据键值key计算hash值,得到插入的数组索引位置 i,如果table[i] == null,直接创建节点,插入到数组下标i位置 ,然后计算是否需要扩容,如果table[i] 不为空,就继续下一步

3,判断table[i] 的首个元素是否和key一样,如果相同(通过hashCode值和equals判断),直接把value覆盖,如果不同,继续下一步

4,判断table[i] 是否是一颗红黑树,如果是红黑树,直接在树上插入键值对数据,否则继续下一步

5,遍历table[i],判断元素的next节点是否为null,如果是null,就把新的节点放入链表中,并让上一个节点的next,指向新节点,接着判断链表的长度是否大于8,如果大于8,把链表转为红黑树

6,插入成功,判断实际的size值是否超过了 扩容的阈值,如果超过,则进行扩容

HashMap中,如何扩容?

通过resize()方法完成扩容

final Node<K,V>[] resize() {
 Node<K,V>[] oldTab = table;
 int oldCap = (oldTab == null) ? 0 : oldTab.length;
 int oldThr = threshold;
 int newCap, newThr = 0;
 if (oldCap > 0) {
 if (oldCap >= MAXIMUM_CAPACITY) {
 threshold = Integer.MAX_VALUE;
 return oldTab;
 }
 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
 oldCap >= DEFAULT_INITIAL_CAPACITY)
 newThr = oldThr << 1; // double threshold
 }
 else if (oldThr > 0) // initial capacity was placed in threshold
 newCap = oldThr;
 else { // zero initial threshold signifies using defaults
 newCap = DEFAULT_INITIAL_CAPACITY;
 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
 }
 if (newThr == 0) {
 float ft = (float)newCap * loadFactor;
 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
 (int)ft : Integer.MAX_VALUE);
 }
 threshold = newThr;
 @SuppressWarnings({“rawtypes”,“unchecked”})
 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
 table = newTab;
 if (oldTab != null) {
 for (int j = 0; j < oldCap; ++j) {
 Node<K,V> e;
 if ((e = oldTab[j]) != null) {
 oldTab[j] = null;
 if (e.next == null)
 newTab[e.hash & (newCap - 1)] = e;
 else if (e instanceof TreeNode)
 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
 else { // preserve order
 Node<K,V> loHead = null, loTail = null;
 Node<K,V> hiHead = null, hiTail = null;
 Node<K,V> next;
 do {
 next = e.next;
 if ((e.hash & oldCap) == 0) {
 if (loTail == null)
 loHead = e;
 else
 loTail.next = e;
 loTail = e;
 }
 else {
 if (hiTail == null)
 hiHead = e;
 else
 hiTail.next = e;
 hiTail = e;
 }
 } while ((e = next) != null);
 if (loTail != null) {
 loTail.next = null;
 newTab[j] = loHead;
 }
 if (hiTail != null) {
 hiTail.next = null;
 newTab[j + oldCap] = hiHead;
 }
 }
 }
 }
 }
 return newTab;
 }
get方法步骤

先计算key的hash值,调用getNode()方法获取到对应的value

getNode() :先计算hash是否在hash表上,如果在桶的首位能找到,直接返回,否则看看是否是红黑树,如果是,就在树中找,不是就遍历链表

remove()方法

通过计算key的hash值删除value

具体通过removeNode()方法实现:

判断桶是否为空,映射的哈希值是否存在,如果在桶的首位找到对应的元素,记录下来,

不在首位就去红黑树或者链表中 去找到,找到对应的节点了,将内容删除

Map.Entry对象

Map中没有迭代器对象,所以Map的遍历只能通过将键值返回到集合中的方式,完成遍历

第一种遍历方式,通过Map子代的keySet()方法和values()方法

keySet()方法用来返回所有的key的set集合,可以用来获取到所有的key

values()方法,用来返回所有的value的集合,然后可以用来获取所有的value

第二种遍历方式,通过Map中的entrySet()方法

Set<Map.Entry<K,V>> entrySet()

这个方法方法,map对象调用后,也是返回一个set集合,但是这个set集合中,存放的是Map.Entry对象

Map.Entry是Map接口的内部接口,里面提供了getKey()和getValue()两个抽象方法,既然存在这两个方法,那么返回的set集合中的每个Entry对象,都可以通过这两个方法,获取键和值

通过查看源码发现,HashMap中的 内部类 Node,实现了Map.Entry接口

static class Node<K,V> implements Map.Entry<K,V>

这个Node实际上又是存放HashMap中键值数据的节点,所以说,其实每个Map.Entry对象就是一对键值数据的存放对象(通过它的自实现类Node来实现数据存放)

那既然实现了Map.Entry接口,Node类必然实现了接口中的方法,getKey()和getValue()方法

因为key和value 是Node类的属性,所以,直接以getter方法的形式返回属性值就可以了。

public final K getKey() { return key; }

public final V getValue() { return value; }

HashMap和HashTable的区别

从存储结构来讲,HashTable除了没有红黑树,其他结构一样,HashTable扩容 是 2n+1,它和HashMap最大的不同是,HashTable是线程安全的,不允许key和value为null,HashTable是一个过时的集合了,所以将来如果需要使用线程安全的HashMap,一般使用ConcurrentHashMap

HashMap总结

1,JDK8中,HashMap底层结构是 :数组+单向链表 + 红黑树, 无序、可以存放null值,null键,键不能重复

2,HashMap的初始容量是16,负载因子默认是0.75,当容量达到16*0.75=12的时候,会扩容

3,初始容量和负载因子都可以自己设置 ,负载因子设置的过大和过小都不太好,

过大的话,会减少扩容,增加哈希冲突,

过小可以减少冲突,扩容次数变多

4,如果需要存入大量元素,那么可以指定一个特定的初始容量,HashMap的容量值都是2的n次方

5,当散列表中链表的数量超过8,并且散列表的容量超过了64,链表会进行转树的操作

LinkedHashMap
继承结构

RangeMap key可以重叠吗_windows_03

注释说明

1,底层是 散列表(数组+链表) + 双向链表

2,允许为null,线程不安全

3,插入顺序有序的

4,负载因子和初始容量影响LinkedHashMap的性能

构造方法

LinkedHashMap()

构造具有默认初始容量(16)和负载因子(0.75)的空插入创建 LinkedHashMap实例。

LinkedHashMap(int initialCapacity)

构造具有指定初始容量和默认负载因子(0.75)的空插入创建 LinkedHashMap实例。

LinkedHashMap(int initialCapacity, float loadFactor)

构造具有指定初始容量和负载因子的空插入创建 LinkedHashMap实例。

LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

accessOrder - 创建模式 - true的访问顺序, false的插入顺序

构造一个空的 LinkedHashMap实例,具有指定的初始容量,负载因子和创建模式。

public class HashMapDemo {
    public static void main(String[] args) {
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
        //添加元素
        linkedHashMap.put("one","hello");
        linkedHashMap.put("two","world");
        linkedHashMap.put("three","java");
        linkedHashMap.put("four","oracle");
        //获取
        System.out.println(linkedHashMap.get("two"));
        //判断
        System.out.println(linkedHashMap.containsKey("one"));
        System.out.println(linkedHashMap.containsValue("hello"));
        //移除
        linkedHashMap.remove("two");
        System.out.println(linkedHashMap);
        //遍历
        Set<String> set = linkedHashMap.keySet();
        for (String key : set) {

            System.out.println(key + " -- " + linkedHashMap.get(key));
        }
        //通过entrySet完成遍历
        

    }
}

accessOrder - 创建模式 - true的访问顺序, false的插入顺序

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        //accessOrder - 创建模式 - true的访问顺序, false的插入顺序
        LinkedHashMap<Integer, String> map =
                new LinkedHashMap(16,0.75f,true);

        int i = 1;
        map.put(i++,"hello1");
        map.put(i++,"hello2");
        map.put(i++,"hello3");
        map.put(i++,"hello4");
        map.put(i++,"hello5");

        //访问之后,元素的顺序被改变
        System.out.println(map.get(3));
        System.out.println(map.get(1));

        //Set<Integer> sets = map.keySet();
        //for (Integer key : sets) {
        //    System.out.println(key + "--" + map.get(key)) ;
        //}
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        for (Map.Entry<Integer, String> entry : entries) {
            System.out.println(entry.getKey() + "--" + entry.getValue());
        }
    }
}
LinkedHashMap总结

LinkedHashMap是HashMap的子类,LinkedHashMap比HashMap多了一个双向链表的结构

LinkedHashMap大部分方法都是来自HashMap

它们之间很多的地方都体现了多态的使用

LinkedHashMap可以设置两种遍历顺序:

访问顺序 和 插入顺序,默认是插入顺序的

TreeMap

RangeMap key可以重叠吗_java_04

注释说明

TreeMap实现了NavigableMap接口,NavigableMap继承了SortedMap,使用TreeMap有序

TreeMap的层是红黑树

线程不同步的

使用Comparator或者Comparable实现键的比较,完成最终数据的排序

构造方法

TreeMap()

使用其键的自然排序构造一个新的空树状图。

TreeMap(Comparator<? super K> comparator)

构造一个新的,空的树图,按照给定的比较器排序。

TreeMap(Map<? extends K,? extends V> m)

构造一个新的树状图,其中包含与给定地图相同的映射,根据其键的 自然顺序进行排序 。

TreeMap(SortedMap<K,? extends V> m)

构造一个包含相同映射并使用与指定排序映射相同顺序的新树映射。

public class TreeMapDemo {
    public static void main(String[] args) {
        //用无参构造创建的TreeMap,默认使用自然排序
        TreeMap<String, String> map = new TreeMap<>();
        //添加元素
        map.put("1","hello");
        map.put("2","java");
        map.put("4","world");
        map.put("5","spring");
        map.put("3","list");

        System.out.println(map);


        //构造TreeMap对象的时候,键必须要可以互相比较
        //如果传入键的时候键的数据类型不一致,可能会导致异常的发生
        TreeMap<Object, String> map1 = new TreeMap<>();
        //map1.put(1,"hello");
        //map1.put("a","java");
        //map1.put(new Student(),"oracle");
        //System.out.println(map1);

        //使用对象作为键,作为键的对象,必须要实现Comparable(比较器)接口
        //重写其中的CompareTo()方法
        TreeMap<Student, String> map2 = new TreeMap<>();
        map2.put(new Student("jack",20,"1班"),"hello");
        map2.put(new Student("lucy",22,"1班"),"oracle");
        map2.put(new Student("zs",22,"2班"),"java");
        map2.put(new Student("lisi",24,"2班"),"mysql");
        map2.put(new Student("wangwu",21,"2班"),"spring");
        System.out.println(map2);
    }
}


public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private String className;

    public Student() {
    }

    public Student(String name, int age, String className) {
        this.name = name;
        this.age = age;
        this.className = className;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        if (name != null ? !name.equals(student.name) : student.name != null) return false;
        return className != null ? className.equals(student.className) : student.className == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        result = 31 * result + (className != null ? className.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", className='" + className + '\'' +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        if (o.age> this.age){
            return -1;
        }
        if (o.age < this.age){
            return 1;
        }
        if (o.name.compareTo(this.name) > 0){
            return -1;
        }
        if (o.name.compareTo(this.name) < 0){
            return 1;
        }


        if (o.className.compareTo(this.className) > 0){
            return -1;
        }
        if (o.className.compareTo(this.className) < 0){
            return 1;
        }
        return 0;
    }

}

构造TreeMap对象的时候,传入Comparator对象

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public User() {
    }

    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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


public class TreeMapDemo01 {
    public static void main(String[] args) {
        TreeMap<User, String> treeMap =
                new TreeMap<>(new Comparator<User>() {
                    @Override
                    public int compare(User o1, User o2) {
                        if (o1.getAge() > o2.getAge()){
                            return 1;
                        }
                        if (o1.getAge() < o2.getAge()){
                            return -1;
                        }
                        if (o1.getName().compareTo(o2.getName()) > 0){
                            return 1;
                        }
                        if (o1.getName().compareTo(o2.getName()) < 0){
                            return -1;
                        }
                        return 0;
                    }
                });

        treeMap.put(new User("jack",20),"java");
        treeMap.put(new User("tom",22),"hello");
        treeMap.put(new User("tony",19),"hello");
        treeMap.put(new User("lucy",22),"hello");

        System.out.println(treeMap);
    }
}
TreeMap的总结

1.底层是红黑树,能够实现该Map集合有序(自然排序)
2.key不能为null
3.可以在构造方法中传入Comparator对象,实现比较方法,也可以让键对象实现Comparator接口,并重写compareTo()方法来实现 键的自然排序
4.TreeMap也是线程不安全的

1,常见的数据结构及特点

栈:先进后出
队列:先进先出
数组:查询速度快,增删速度慢
链表:查询速度慢,增删速度快
树:每个节点有0个或多个子节点;没有父节点的节点叫做根节点;每一个非父节点,有且仅有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树;增删查询都很快

2,Collection的体系结构

列表(List)

集(Set)

映射(Map)

栈(Stack)

队列(Queue)

RangeMap key可以重叠吗_System_05

3,List集合的特点,Map的特点

List接口是Collection接口的子接口,表示有序的序列,可以通过索引访问到List中的元素
List中可以存放 重复的元素 ,并且可以存放null,存放多个null
List接口中提供了 ListIterator,除了实现Iterator接口中的往后遍历的方法,还写了往前遍历的方法
Map一次存储两个元素,一个是key(键),一个是value(值)
Map中的key(键)是唯一的,
Map中的value(值)是可以重复的,可以有null值

4,ArrayList的特点

可变数组实现List接口,相当于一个动态数组实现所有方法
可以存放任意类型的元素,包括null
创建ArrayList会有一个容量,来定义将来存放元素的数组的大小

5,HashMap的特点

键值对存储
快速查询
无序存储
不允许键重复
允许存放空键和空值
非线程安全
容量可调
迭代顺序不确定