Java——集合类(集合输出与Map)

目录

1.集合输出

1.1 迭代输出:Iterator(重要) 

1.2 双向迭代接口:ListIterator

1.3 Enumeration枚举输出 

1.4 foreach输出

2. Map接口

2.1 HashMap接口

2.2 Hashtable

2.3  ConcurrentHashMap子类 

2.4 Map集合使用Iterator输出(重点) 

2.5 关于Map中key的说明

2.6 TreeMap子类

 


1.集合输出

集合的标准输出一共有四种手段:Iterator(单向迭代器)、ListIterator(双向迭代器)、Enumeration、foreach

  • 1.1 迭代输出:Iterator(重要) 

只能够由前向后进行内容的迭代处理

  •  三个重要方法
  1. 判断是否有下一个元素: public boolean hasNext();
  2. 取得当前元素: public E next();
  3. 删除元素: public default void remove();
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * 遍历中删除元素
 * Author:qqy
 */
public class Test10 {
    public static void main(String[] args) {
        Set<Person1> people = new TreeSet<>();
        people.add(new Person1("Jack", 22));
        people.add(new Person1("Alice", 18));
        
        Iterator<Person1> iterator = people.iterator();
        while (iterator.hasNext()) {
            //需要先next,再remove
            //满足条件 -> 删除
            Person1 p = iterator.next();
            if (p.getName().equals("Jack")) {
                System.out.println(p);
                //一个hasNext()后不能iterator.next()两次,两者成对出现,否则游标不变
//                System.out.println(iterator.next());
                iterator.remove();
            }
        }

        System.out.println(people);
    }
}
  • 1.2 双向迭代接口:ListIterator

 Iterator的子接口

  • 两个重要方法
  1. 判断是否有上一个元素:public boolean hasPrevious();
  2. 取得上一个元素:public E previous();
  3. 将指定元素插入:boolean add(E e);
  4. 用指定的元素替换由 next()或 previous()返回的最后一个元素:E set(int index, E element);
  • 1.2.1 迭代器内容的修改

修改迭代器内容时(set()),不能写在next()之前

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

/**
 * 迭代
 * Author: qqy
 */
public class Test3 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("bonjour");
        ListIterator<String> iterator = list.listIterator();
        while (iterator.hasNext()) {
            //iterator.add("+");
            //修改迭代器内容set()时,不能写在next()之前,
//            iterator.set("+"); //error
            System.out.print(iterator.next() + ", ");  //hello, bonjour,
            //iterator.add("+");
//            System.out.print(iterator.next() + ", ");  //hello, bonjour,
            iterator.set("+");
            //对集合进行遍历并删除,用迭代器
            //iterator.remove();
        }
        System.out.println();
        for (String item : list) {
            System.out.print(item + ", ");  //+, +,
        }
    }
}
  • 1.2.2 ListIterator的前后遍历

使用hasPrevious()前,要确定游标不位于集合的头,否则读取不到数据

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

/**
 * ListIterator的前后遍历
 * Author:qqy
 */
public class Test5 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");

        //list.listIterator() -> 返回列表中的列表迭代器(ArrayList的方法)
        ListIterator<String> iterator = list.listIterator();
        //  A  B  C  D
        //^             (迭代器的初始位置)
        
        //A -> B -> C -> D -> 
        System.out.println("从前往后迭代:");
        while (iterator.hasNext()) {
            System.out.print(iterator.next());
            System.out.print(" -> ");
        }                                     
        System.out.println(); 

        //当游标在后面时,才能从后向前遍历
        //D <- C <- B <- A <- 
        System.out.println("从后往前:");
        while (iterator.hasPrevious()) {
            System.out.print(iterator.previous());
            System.out.print(" <- ");
        }
    }
}
  • 1.3 Enumeration枚举输出 

  • 三个重要方法
  1. 判断是否有下一个元素:public boolean hasMoreElements();
  2. 取得元素:public E nextElement(); 
  3. 取得Enumeration接口对象:public Enumeration elements() 
/**
 * Enumeration
 * Author:qqy
 */
public class Test11 {
    public static void main(String[] args) {
        Vector<String> vector = new Vector<>();
        vector.add("Java");
        vector.add("C++");
        vector.add("Python");
        Enumeration enumeration = vector.elements();
        while (enumeration.hasMoreElements()) {
            System.out.println(enumeration.nextElement());
        }
    }
}
  • 1.4 foreach输出

遍历集合不可修改,否则会出现ConcurrentModificationException(并发修改异常)

import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * foreach遍历集合
 * Author:qqy
 */
public class Test9 {
    public static void main(String[] args) {
        Set<Person1> people = new TreeSet<>();
        people.add(new Person1("Jack", 22));
        people.add(new Person1("Alice", 18));

        //for、while、foreach方式遍历集合(List,Set ...)
        //遍历依赖于迭代器
        //若修改集合,会出现ConcurrentModificationException(并发修改异常),线程不安全
        for (Person1 person : people) {
            //people.add(new Person1("Tom", 23));
            System.out.println(person);
        }

        //遍历集合:Iterator 可以 add remove
       Iterator<Person1> iterator = people.iterator();
        while (iterator.hasNext()) {
//            people.add(new Person1("Tom", 23));
            //需要先next,再remove
            System.out.println(iterator.next());
            iterator.remove();
        }
        System.out.println(people);
    }
}

 class Person1 implements Comparable<Person1>{

     private String name;
 
     private Integer age;

     public Integer getAge() {
         return age;
     }

    public Person1(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

     @Override
     public int compareTo(Person1 o) {
         return this.age - o.getAge();
     }
 }

2. Map接口

顶层接口,Map中会一次性保存两个对象,且这两个对象的关系:key=value结构。可以通过key找到对应的value内容。 

  • 四个常用子类: HashMap、 Hashtable、TreeMap、ConcurrentHashMap 

  • 2.1 HashMap接口

  • 2.1.1 基本操作

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

/**
 * Map基本操作
 * Author:qqy
 */
public class Test {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();

        map.put("1", "java");
        map.put("2", "C++");
        map.put("3", "Python");

        System.out.println(map.containsKey("1"));//true
        System.out.println(map.containsKey("4"));//false
        System.out.println(map.get("1"));//java

        //1.HashMap key value 是可以为null
        map.put(null, "key is null");
        map.put("4", null);
        map.put("4", "PHP");
        System.out.println(map.size());//5  ∵key相等,对应的value会被覆盖掉
        System.out.println(map.get("4"));//PHP
        System.out.println(map.get("6"));//null  -> key不存在,get获得null

        //获取map中的所有key
        System.out.println("获取所有的key:");
        Set<String> keys = map.keySet();
        for (String key : keys) {
            System.out.println(key);
        }

        System.out.println("输出key=value:");
        for (String key : keys) {
            System.out.println(key + "=" + map.get(key));
        }

        System.out.println("输出所有的value:");
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }
    }
}
  • 2.1.2 源码解析
  • 基本实现

HashMap可以看作数组和链表的复合结构,数组被分为一个个桶,每一个桶都被键值对所确定,哈希值相同的键值对,以链表的形式存储。

HashMap负责平衡桶的个数和桶中元素的个数,当两者个数都达到一定程度,则将链表树化。树的检索效率是极高的,但同时树的维护成本也是极高的。桶数量的改变会带来一定的性能损耗。

  • 属性:

最大容量  MAXIMUM_CAPACITY:2^30

负载因子  DEFAULT_LOAD_FACTOR:0.75f

树化阈值  TREEIFY_THRESHOLD:8     

最小树化容量  MIN_TREEIFY_CAPACITY:64     

桶数量超过64 && 链表数量超过8 -> 树化为红黑树

  • 方法

   无参构造:只设置了负载因子,延迟加载,在put()中开辟空间,初始化容量16,阈值12

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public V put(K key, V value) {
    //int类型的hash码;key(泛型);value;如果不存在,加入,如果存在,替换;剔除
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
    int h;
    //key等于null,为0;不等于null,调用对象的hashCode()
    //将h的高16位和低16位进行异或,取得最终的hash码
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

   resize()扩容

  1. 创建初始存储表
  2. 在容量不满足需求的时候,进行扩容
  3. 扩容后,需要将老的数组中的元素重新放置到新的数组

   remove()

public V remove(Object key) {
    Node<K,V> e;
    //通过key删除
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

  有参构造

//传初始化容量
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
  • 容量、负载系数、树化
  1. 容量和负载因子决定了可用的桶的数量
  2. 负载因子 * 容量 > 元素数量  -> 容量需要 > 预估元素数量 / 负载因子,且是2的幂数
  3. 树化:如果容量小于 MIN_TREEIFY_CAPACITY,只会进行简单的扩容。如果容量大于 MIN_TREEIFY_CAPACITY ,则会进行树化改造。 
  • 2.2 Hashtable

  • Hashtable和HashMap的区别

  • 2.3  ConcurrentHashMap子类 

利用Collections将HashMap包装成线程安全的集合。

  • 早期ConcurrentHashMap

将桶进行分段(Segment),段的数量由并发等级确定,被自动调整到2的幂数值。构造且没有指定任何参数时,桶的默认初始化数量是16;指定了初始化容量的同时也指定了并发等级时,若容量小于并发等级,则将并发等级的大小作为容量。

  • java8和之后的版本,ConcurrentHashMap变化
    • 延迟加载
    • 数据存储利用 volatile 来保证可见性 -> 立即可见,保证数据一致性
    • 使用 CAS(比较和赋值) 等操作,在特定场景进行无锁并发操作,性能更高
    • 底层优化常用手段:优化JDK的内部类、对性能有极高要求的类使用本地方法、内存上的优化:扩大内存,提高缓存
  • 2.4 Map集合使用Iterator输出(重点) 

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

/**
 * Author:qqy
 */
public class Test1 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();

        map.put("1", "java");
        map.put("2", "C++");
        map.put("3", "Python");

        //遍历map的三种方法
        // keySet -> get  ;
        // 只获取value:values ;
        // entrySet(foreach)

        System.out.println("通过Entry遍历map");
        Set<Map.Entry<String, String>> entries = map.entrySet();
        //foreach、iterator ,Set没有下标,不能使用for循环
        Iterator<Map.Entry<String, String>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }

        //优化
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}
  • 2.5 关于Map中key的说明

通过key计算hashCode,再利用hashCode计算桶的下标。

  • 采用自定义类作为key时,一定要记得覆写Object类的hashCode()与equals()方法。
     

  • 2.6 TreeMap子类

TreeMap是一个可以排序的Map子类,它是按照Key的内容排序的。

  1. key是系统(JDK / 第三方)定义的类,从小到大排序,若要修改,构造TreeMap的构造方法传入比较器(Comparator)接口的实现对象(或用lambda表达式 )
  2. key是自定义的类,可以实现比较接口Comparable,也可以不实现,在使用时指定比较器(Comparator)接口的实现类对象
  3. 使用时指定Comparator接口比较灵活
import java.util.Map;
import java.util.TreeMap;

/**
 * TreeMap
 * Author:qqy
 */
public class Test2 {
    public static void main(String[] args) {        
        //更改比较器,逆序
        Map<Integer, String> map = new TreeMap<>((o1, o2) -> o1.compareTo(o2) * -1);

        map.put(1, "Java");
        map.put(2, "C++");
        map.put(4, "PHP");
        map.put(3, "C"); //由key决定如何排序,从大到小的排序
        System.out.println(map);
    }
}
  • 有Comparable出现的地方,判断数据就依靠compareTo()方法完成,不再需要equals()与hashCode() 

Map总结:

  1. Collection保存数据的目的一般用于输出(Iterator),Map保存数据的目的是为了根据key查找,找不到返回null。
  2. Map使用Iterator输出(Map.Entry的作用)
  3. HashMap数据结构一定要理解(链表、红黑树和桶)、HashMap与Hashtable区别 

 

猜你喜欢

转载自blog.csdn.net/qq_42142477/article/details/87891026
今日推荐