JavaSE集合

Java集合

Java集合框架(Java Collections Framework,JCF)是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。

集合与数组都是对多个数据进行存储操作的结构,简称Java容器(存储主要指内存层面,不涉及持久层)

数组存储的特点

  • 初始化后,长度不可改
  • 数组一旦定义好,其元素的类型也确定了

数组存储的缺点

  • 一旦初始化后,长度不可改
  • 数组中提供的方法非常有限,对于添加、删除、插入等操作不方便,效率较低
  • 获取数组中实际元素的个数的需求,没有现成方法可用
  • 对于无序、不可重复的需求,无法满足

故提出集合框架用于增强对数据的处理!

一、Collection接口

1.1 Collection 常用方法

  • contains(Object obj)方法:判断当前集合中是否包含obj

    注意:Collection接口的实现类的对象中添加数据(对象)时,要求对象所在类重写equals()方法

    @Test
    public void test1() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("LEIZ"));
        coll.add(false);
        coll.add(new Person("Jerry", 20));
    
        //contains(Object obj) 判断当前集合中是否包含obj
        boolean contains = coll.contains(123);
        System.out.println(contains);
        //判断的是内容,不是地址,使用equals方法
        System.out.println(coll.contains(new String("JS")));        
    }
    
  • containsAll(Collection coll)方法:判断形参coll中所有元素是否都存在于当前集合

    @Test
    public void test2() {
        Collection coll1 = new ArrayList();
        coll1.add(123);
        coll1.add(456);
        coll1.add("Hi");
        Collection coll = Arrays.asList(123, 456, "Hello");
        //coll1的元素是否都存在与coll中
        System.out.println(coll.containsAll(coll1));    //false
    }
    
  • remove(Object obj)方法:从当前集合中删除obj元素,成功true,失败false

    @Test
    public void test3() {
        Collection coll1 = new ArrayList();
        coll1.add(123);
        coll1.add(456);
        coll1.add("Hi");
        Collection coll = Arrays.asList(123, 456, "Hello");
        //coll1的元素是否都存在与coll中
        coll1.remove("Hi");     //将Hi删除
        System.out.println(coll.containsAll(coll1));    //true
    }
    
  • removeAll(Collection coll1)方法:从当前集合中移除coll1中所有的元素(差集)

    @Test
    public void test4() {
        Collection coll1 = new ArrayList();
        coll1.add(123);
        coll1.add(456);
        coll1.add("Hi");
        Collection coll = Arrays.asList(123, 456, "Hello");
        System.out.println(coll1);
        System.out.println(coll);
        coll1.removeAll(coll);
        System.out.println(coll1);
        System.out.println(coll);
    }
    
  • retainAll(Collection coll1)方法:获取当前集合和coll1集合的交集,并返回给当前集合

    @Test
    public void test5() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add("Hi");
        Collection coll1 = Arrays.asList(123, 456, "Hello");
        coll.retainAll(coll1);
        System.out.println(coll);
    }
    
  • equals(Object obj)方法:比较当前集合和形参集合元素是否相同

    @Test
    public void test6() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        Collection coll1 = Arrays.asList(456, 123);
        System.out.println(coll.equals(coll1));
    }
    
  • hashCode()方法:返回当前对象的哈希值

    @Test
    public void test7() {
        Collection coll = Arrays.asList("Hello", "world");
        System.out.println(coll.hashCode());    //-2023748383
    }
    
  • toArray()方法:将集合转换为数组

    @Test
    public void test8() {
        Collection coll = Arrays.asList("Hello", "world");
        System.out.println(coll);
        Object[] objects = coll.toArray();
        for (Object object : objects) {
            System.out.print(object + " ");
        }
    }
    

    数组 ---> 集合: Arrays.asList(new String[] {"AA", "BB", "CC"});

  • iterator():迭代器,遍历元素

1.2 iterator迭代器

Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素

迭代器模式:提供一种方法访问一个容器对象中各个元素,且不暴露该对象的内部细节

迭代器模式就是为容器而生!

Iterator仅用于遍历集合!集合对象每次调用iterator()方法都得到一个全新的迭代器对象

1.2.1 Iterator接口的方法
  • hasNext:如果迭代具有更多的元素,则返回true

    @Test
    public void test3() {
        Collection coll = Arrays.asList("Hello", "World", "LEIZ", 123);
        Iterator iterator = coll.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
    
  • next:返回迭代中的下一个元素。

    @Test
    public void test2() {
        Collection coll = Arrays.asList("Hello", "World", "LEIZ", 123);
        Iterator iterator = coll.iterator();
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
    }
    
  • remove:从底层集合中删除此迭代器返回的最后一个元素(可选操作)。

    @Test
    public void test4() {
        List<Object> list = new ArrayList<>();
        list.add(123);
        list.add(456);
        list.add("Hello");
        list.add("LEIZ");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object o = iterator.next();
            if ("LEIZ".equals(o)) {
                iterator.remove();
            }
        }
        iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
    

    注意:此remove可以在遍历时,删除集合中的元素,不同于集合中直接调用remove

    如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove都会报错误

1.3 iterator应用-foreach循环

  • 遍历操作不需要获取Collection或者数组的长度,无需使用索引访问元素
  • 遍历集合的底层调用Iterator完成操作
for (数据类型 自定义名称 : 要遍历的结构名称) {
    System.out.printIn(自定义名称.getName());
}

二、Collection子接口--List接口

List集合类中元素有序,且可以重复,集合中的每个元素都有其对应的顺序索引

List接口 常用的实现类有 ArrayList、LinkedList、Vector

2.1 ArrayList

  • List接口的主要实现类,线程不安全、效率高,底层使用Object[]存储
  • 本质上,ArrayList是对象引用的一个“变长”数组

原码分析

JDK1.7 版本时:
    
ArrayList list = new ArrayList();	-- 底层创建了长度为10的Object[]数组elementData
list.add(123);  -- elementData[0] = new Integer(123);
...
list.add(11);  -- 若此次的添加导致底层elementData数组容量不够,则扩容
-- 默认情况下,扩容到原来的1.5倍,同时将原有数组中的数据复制到新的数组
    
JDK1.8 版本时:
ArrayList list = new ArrayList();	-- 底层Object[] elementData初始化{},并没有创建长度
list.add(123);	-- 第一次调用add()时,底层才创建长度为10的数组,并将123添加到elementData数组
-- 后序和1.7相同

jdk7中的ArrayList的对象的创建类似于单例的饿汉式,jdk8中ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

2.2 LinkedList

  • 底层使用双向链表存储,对于插入和删除,此类的效率很高

原码分析

LinkedList list = new LinkedList();	--	内部声明了Node类型的frist和Last属性,默认值为null
lias.add(123);	-- 将123 封装到Node中,创建了Node对象

    Node定义为
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2.3 Vector

  • List接口的古老实现,线程安全、效率低,底层使用Object[]存储
  • Vector总是比ArrayList慢,尽量避免使用

jdk1.7和1.8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来的二倍

注意:三个实现类都实现了List接口、存储数据的特点是相同的,存储有序的,可重复的数据

2.4 List常用方法

2.5 常见面试题

@Test
public void test1() {
    List<Object> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    updateList(list);
    System.out.println(list);
}

public static void updateList(List list) {
    list.remove(2);
}

注意:删除的是3,2指索引,若想直接删除元素,使用 Integer

三、Collection子接口--Set接口

Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加失败(相同的元素只能添加一个)

Set判断两个对象是否相同不能使用 == 运算符,而是根据 equals() 方法

Set集合存储是无序的!(无序 != 随机性,而是相对于List的无序)

3.1 HashSet

  • HashSet是Set接口的典型实现,大多数时候使用Set集合是都使用这个实现类

  • HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能

  • HashSet具有以下特点

    • 不能保证元素的排列顺序
    • 线程不安全
    • 集合元素可以是null
  • HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等

  • 对于存放在Set容器中的对象,对应的类一定要重写equals()方法 和 hashCode(Object obj)方法,以实现对象相等规则(即:相等的对象必须具有相等的散列码)

@Test
public void test1() {
    HashSet<Object> set = new HashSet<Object>();
    set.add(5);
    set.add(new Person("LEI",22));
    set.add(4);
    set.add(3);
    set.add(new Person("LEI",20));
    set.add(2);
    set.add(new Person("LEI",21));
    set.add(1);
    Iterator<Object> iterator = set.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

注意:HashSet存储的数据在底层数据中并非按照数组索引的顺序添加!而是按照数据的哈希值决定的!

HashSet添加元素

  • 向HashSet中添加元素 123 时,先调用元素 123 所在类的hashCode()方法,计算元素 123 的哈希值!

  • 此哈希值通过某种计算方法,算出在HashSet底层数据中存放的位置(即索引位置)

  • 再判断数组此位置上是否存在了元素

    • 若存在,则比较哈希值,不相同则存入,若相同。进而调用equals方法,查看方法的返回值,true则添加成功,false则添加失败

      jdk1.7中,将要添加的元素指向原来的元素
      jdk1.8中,使原来的元素指向要添加的元素
      
    • 若不存在,则存入

  • HashSet底层:数组 + 链表

hashCode与equals的重写

重写的基本原则

  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值
  • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等
  • 复写equals方法的时候一般都需要同时复写hashCode方法
  • 通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Person)) return false;
    Person person = (Person) o;
    return age == person.age &&
        Objects.equals(name, person.name);
}

@Override
public int hashCode() {
    return Objects.hash(name, age);
}
public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

选择数字31的原因

  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的 “冲突”就越少,查找起来效率也会提高(减少冲突)
  • 31只占用5bits,相乘造成数据溢出的概率较小
  • 31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化(提高算法效率)
3.1.1 LinkedHashSet
  • 作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历
  • 添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
  • 对于比较频繁的遍历操作,效率更高!
@Test
public void test2() {
    Set<Object> set = new LinkedHashSet<Object>();
    set.add(5);
    set.add(new Person("LEI",22));
    set.add(4);
    set.add(3);
    set.add(new Person("LEI",20));
    set.add(2);
    set.add(new Person("LEI",21));
    set.add(1);
    Iterator<Object> iterator = set.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

3.2 TreeSet

  • 有序,查询比List快,底层使用红黑树实现

  • 可以按照添加对象的指定属性,进行排序

  • 向TreeSet中添加的数据,要求是相同类的对象

默认按照从小到大排序

@Test
public void test3() {
    Set<Integer> set1 = new TreeSet<>();
    set1.add(2);
    set1.add(5);
    set1.add(0);
    for(Integer o: set1) {
        System.out.println(o);
    }
}
3.2.1 自然排序

实现Comparable接口,implements Comparable

//按照年龄升序排序
@Override
public int compareTo(Object o) {
    if(o instanceof Person) {
        Person person = (Person)o;
        return Integer.compare(this.age, person.age);
    }else {
        throw new RuntimeException("输入的类型不匹配");
    }
}
@Test
public void test4() {
    Set<Person> set1 = new TreeSet<>();
    set1.add(new Person("LEI",10));
    set1.add(new Person("LEI1",11));
    set1.add(new Person("LEI2",9));
    for(Person o: set1) {
        System.out.println(o);
    }
}

按姓名排序,名字重复继续按照年龄排序

//按照年龄升序排序
@Override
public int compareTo(Object o) {
    if(o instanceof Person) {
        Person person = (Person)o;
        int compare = this.name.compareTo(person.name);
        if(compare != 0) {
            return compare;
        }else {
            return Integer.compare(this.age, person.age);
        }
    }else {
        throw new RuntimeException("输入的类型不匹配");
    }
}
3.2.2 定制排序

Comparator

@Test
public void test5() {
    Comparator<Person> com = (o1, o2) -> {
        if(o1 != null && o2 != null) {
            return Integer.compare(o1.getAge(), o2.getAge());
        }else {
            throw new RuntimeException("输入的数据类型不匹配");
        }
    };

    Set<Person> set1 = new TreeSet<>(com);
    set1.add(new Person("LEI",10));
    set1.add(new Person("LEI1",11));
    set1.add(new Person("LEI2",9));
    set1.add(new Person("LEI2",8));
    for(Person o: set1) {
        System.out.println(o);
    }
}

四、Map接口

Map接口与Collection并列存在。用于保存具有映射关系的数据:key - value,其中 key 与 value 都可以是任何引用类型的数据

Map中的 key 使用 Set 存放,不允许重复!即同一个Map对象所对应的类,必须重写hashCode()和eauals()方法,一般常用String类作为Map的“键”

Key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的value

Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap、Properties

4.1 Map接口的常用方法

  • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
  • void putAll(Map m):将m中的所有key-value对存放到当前map中
  • Object remove(Object key):移除指定key的key-value对,并返回value
  • void clear():清空当前map中的所有数据
  • Object get(Object key):获取指定key对应的value
  • boolean containsKey(Object key):是否包含指定的key
  • boolean containsValue(Object value):是否包含指定的value
  • int size():返回map中key-value对的个数
  • boolean isEmpty():判断当前map是否为空
  • boolean equals(Object obj):判断当前map和参数对象obj是否相
  • Set keySet():返回所有key构成的Set集合
  • Collection values():返回所有value构成的Collection集合
  • Set entrySet():返回所有key-value对构成的Set集合
@Test
public void test1() {
    Map<String, Object> map = new HashMap<>();
    map.put("No1" , new User(1, "LEIZ"));
    map.put("No2" , new User(2, "JSSB"));
    map.put("No3" , new User(3, "JS2B"));
    map.put("No4" , new User(4, "JSSSB"));

    System.out.println(map);

    Map<String, Object> map1 = new HashMap<>();
    map1.putAll(map);
    //以上可以替换为 -- Map<String, Object> map1 = new HashMap<>(map);
    System.out.println(map1);

    map.remove("No4", new User(4, "JSSSB"));
    System.out.println(map);

    System.out.println(map.values());
    System.out.println(map.keySet());
}

4.2 Map结构的理解

  • Map 中的key:无序,不可以重复,使用Set存储所有的 key(需重写两个方法)
  • Map 中的value:无序,可重复,使用Collection存储所有的 value
  • key - value 构成了 一个 Entry 对象
  • Entry 是无序的、不可以重复的,使用Set 存储所有的 Entry

五、Map实现类--HashMap

  • HashMap是Map接口使用频率最高的是西安类
  • 允许使用null键和null值,与HashSet一样,不保证映射的顺序
  • HashMap 判断两个 key 相等的标准:两个 key 通过 equals() 方法返回 true, hashCode 值也相等。
  • HashMap 判断两个 value相等的标准:两个 value 通过 equals() 方法返回 true
@Test
public void test1() {
    Map<Integer, String> map = new HashMap<>();
    map.put(1,"jssb");
    map.put(2,"jssb1");
    map.put(3,"jssb2");
    map.put(4,"jssb3");
    map.put(6,"jssb6");
    map.put(5,"jssb5");
    Set<Map.Entry<Integer, String>> set = map.entrySet();
    for (Map.Entry<Integer, String> integerStringEntry : set) {
        System.out.println(integerStringEntry);
    }
}

5.1 HashMap存储结构

JDK 7 之前的版本:数组 + 链表

JDK 8以后:数组 + 链表 + 红黑树

5.2 HashMap的底层实现原理

  • HashMap原码中重要的常量

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16

  • MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30

  • DEFAULT_LOAD_FACTOR:HashMap的默认加载因子

  • **TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树 **

  • **UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表 **

  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的 数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行 resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4 倍。)

  • table:存储元素的数组,总是2的n次幂 entrySet:存储具体元素的集

  • **size:HashMap中存储的键值对的数量 **

  • **modCount:HashMap扩容和结构改变的次数。 **

  • threshold:扩容的临界值,=容量*填充因子

  • **loadFactor:填充因子 **

5.2.1 底层实现过程
Map map = new HashMap();		-- 实例化以后,底层创建了长度是16的一维数组Entry[] table
... 执行put ...
map.put(key1, value1);		-- 首先调用key1所在类的HashCode()计算key1哈希值,此哈希值经过某种算法
                               计算后,得到Entry 数组中的存放位置,若此位置的数据为空,则 key1 - 	                                value 直接添加成功。(情况一)
                               若此位置不为空,则意味着此位置有数据(可能为多个,以链表形式存在)比较                                  key1和已经存在的一个或多个数据的哈希值
                                   若key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1
                                   则添加成功(情况二)
                                   若key1的哈希值和 某一个 数据的哈希值相同了,继续比较eauals方法,
                                   继续比较
                                        若equals返回false,意味着还是不同的,添加成功!(情况三)
                                        若返回true,则一模一样,使用value1去替换相同key的value值
    
                            -- 对于情况二、三 :此时key1 - value1 和原来的数据以链表的形式存储
    
-- 在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来

-- JDK8相较于JDK7在底层实现方面的不同:
    1.new HashCode():底层没有创建一个长度为16的数组
    2.JDK8底层的数组是Node[],并不是Entry[]
    3.首次调用put()方法时,底层去创建长度为16的数组
    4.JDK7底层结构只有数组+链表,8出现红黑树
    5.当某一个索引位置上的元素以链表的形式存在的数据个数 > 8,且当前数组的长度 > 64时,此时索引位置上的所有
      数据更改为使用红黑树存储
    
5.2.2 扩容

JDK7版本

HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时, 系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个 bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。

每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引 用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。 而且新添加的元素作为链表的head

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的 长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在 HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算 其在新数组中的位置,并放进去,这就是resize。

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)*loadFactor 时 , 就会进行数组扩容

loadFactor 的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况 下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数 超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把 数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元素在数组中的位置, 而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数, 那么预设元素的个数能够有效的提高HashMap的性能。

JDK8版本

HashMap的内部存储结构其实是数组+链表+树 的结合。当实例化一个 HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系 时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表 中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为 “桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查 找bucket中的元素。

每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带 一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能 生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象 可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个 TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数 size)loadFactor 时 , 就会进行数组扩容, loadFactor 的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认 情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中 元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值) 的时候,就把数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元 素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有 达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成 树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后, 下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

映射关系的key是否可以修改?answer:不要修改

映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算 每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关 系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。

5.3 总结(JDK8的变化)
  • HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
  • 当首次调用map.put()时,再创建长度为16的数组
  • **数组为Node类型,在jdk7中称为Entry类型 **
  • **形成链表结构时,新添加的key-value对在链表的尾部(七上八下) **
  • 当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置 上的所有key-value对使用红黑树进行存储
5.3 常见问题

负载因子值的大小,对HashMap有什么影响?

  • 负载因子的大小决定了HashMap的数据密度
  • 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长, 造成查询或插入时的比较次数增多,性能会下降
  • 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的 几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间
  • 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此 时平均检索长度接近于常数

六、Map其他实现类

6.1 LinkedHashMap

  • LinkedHashMap 是 HashMap 的子类
  • 在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
  • 与LinkedHashSet类似,LinkedHashMap可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致
@Test
public void test2() {
    Map<Integer, String> map = new LinkedHashMap<>();
    map.put(1,"jssb");
    map.put(2,"jssb1");
    map.put(3,"jssb2");
    map.put(4,"jssb3");
    map.put(6,"jssb6");
    map.put(5,"jssb5");
    Set<Map.Entry<Integer, String>> set = map.entrySet();
    for (Map.Entry<Integer, String> integerStringEntry : set) {
        System.out.println(integerStringEntry);
    }
}

6.2 TreeMap

  • TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序,其可以保证所有的 Key-Value 对处于有序状态
  • TreeMap 的 Key 的排序
    • 自然排序::TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
    • 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或 者compare()方法返回0

方式一、继承Comparable类

@Override
public int compareTo(Object o) {
    if(o instanceof User) {
        User user = (User)o;
        int compare = this.name.compareTo(user.name);
        if(compare != 0) {
            return compare;
        }else {
            return Integer.compare(this.age,user.age);
        }
    }else {
        throw new RuntimeException("输入类型不匹配");
    }
}
@Test
public void test3() {
    Map<Object, Integer> map = new TreeMap<>();
    User user1 = new User("zl1", 22);
    User user2 = new User("zl1", 13);
    User user3 = new User("zl2", 14);
    User user4 = new User("zl3", 25);
    map.put(user1, 98);
    map.put(user2, 89);
    map.put(user3, 76);
    map.put(user4, 100);

    Set<Map.Entry<Object, Integer>> set = map.entrySet();
    for (Map.Entry<Object, Integer> next : set) {
        System.out.println(next.getKey() + "----->" + next.getValue());
    }
}

方式二、定制排序

@Test
public void test4() {
    Map<Object, Integer> map = new TreeMap<>(new Comparator<Object>() {

        @Override
        public int compare(Object o1, Object o2) {
            if(o1 instanceof User && o2 instanceof User) {
                User user = (User)o1;
                User user2= (User)o2;
                return Integer.compare(user.getAge(),user2.getAge());
            }
            throw new RuntimeException("输入的类型不匹配!");
        }
    });
    User user1 = new User("zl1", 22);
    User user2 = new User("zl1", 13);
    User user3 = new User("zl2", 14);
    User user4 = new User("zl3", 25);
    map.put(user1, 98);
    map.put(user2, 89);
    map.put(user3, 76);
    map.put(user4, 100);

    Set<Map.Entry<Object, Integer>> set = map.entrySet();
    for (Map.Entry<Object, Integer> next : set) {
        System.out.println(next.getKey() + "----->" + next.getValue());
    }
}

6.3 Hashtable

  • Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap, Hashtable是线程安全的
  • Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用
  • 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
  • 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
  • Hashtable判断两个key相等、两个value相等的标准,与HashMap一致

6.4 Properties

  • Properties 类是 Hashtable 的子类,该对象用于处理属性文件
  • 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
  • 存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法
public class PropertiesTest {
    public static void main(String[] args) throws Exception {
        Properties pros = new Properties();
        FileInputStream fis = new FileInputStream("db.properties");
        pros.load(fis);   //加载流文件
        String user = pros.getProperty("username");
        String pass = pros.getProperty("password");

        System.out.println("username = " + user + ", password = " + pass);
    }
}

七、Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类

Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

猜你喜欢

转载自www.cnblogs.com/yfyyy/p/12725618.html
今日推荐