Java初学笔记(五):集合类

12 篇文章 0 订阅

集合类

0、简介

  1. java.util包中主要提供了三中集合类型:

    • List: 有序列表的集合
    • Set: 没有重复元素的集合
    • Map: 通过键值(key-value)查找的映射表集合
  2. 关于java.util.Collection: Collection是除Map外的所有其它集合类的根接口

  3. java集合的特点:

    • 一, 接口和实现类分离
    • 二, 支持泛型
    List[] li = new ArrayList[]; // 可以放任意类型数据
    List<String>[] = new ArrayList<>[]; // 只能存放String类型的数据
    
  4. java集合支持通过统一的方式进行访问: 迭代器(Iterator)

  5. 部分集合属于历史遗留, 不应该继续使用:

    • Hashtable: 一种线程安全的Map实现
    • Vector: 一种线程安全的List实现
    • Stack: 基于Vector实现的LIFO的栈
    • Enumeration<E>: 集合接口, 已被Iterator<E>取代

1、List

  1. List是一种有序链表

  2. List<E>接口:

    • 末尾添加元素: void add(E e)
    • 指定位置插入元素: void add(int index, E e)
    • 删除指定位置的元素: int remove(int index)
    • 删除指定元素: int remove(Object e)
    • 获取指定位置的元素: E get(int index)
    • 获取列表大小: int size()
  3. ArrayList通过数组实现List, LinkedList通过链表实现List

  4. List可以添加重复元素, 可以添加null

  5. 创建List

    List<String> li = new ArrayList<>();
    List<Float> li = new LinkedList<>();
    List<Integer> li = List.of(1, 2, 3);
    
  6. 列表的遍历, 不推荐使用循环序号遍历, 因为对LinkedList链表效率很差

  7. 使用迭代器Iterator: 内部实现总是采用最高效的方式迭代, 调用方不用关心内部结构

     import java.util.Iterator;
     import java.util.List;
     public class Main {
         public static void main(String[] args) {
             List<String> list = List.of("apple", "pear", "banana");
             for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
                 String s = it.next();
                 System.out.println(s);
             }
    
             for (String s: list) {  // 使用for each方法自动调用iterator
                 System.out.println(s);
             }
         }
     }
    
  8. ListArray转换:

    • li.toArray()方法返回Object[]数组, 数据类型会丢失
    • li.toArray(T[])方法返回T[]数组, List会把元素复制到T[]并返回:
      • Integer[] arr = li.toArray(new Integer[3]);
      • 如果传入T[]类型不匹配, 会抛出ArrayStoreException
      • T[]大小不够则List内部创建新的T[], T[]有多余则多余的位置全填充null
      • Integer[] arr = li.toArray(new Integer[li.size()]);
    • List<> li = List.of(arr), 注意: 该方法返回的是一个只读List, 修改时会抛出UnsupportedOperationException
equals
  • List提供了接口:
    • boolean contains(Object e)判断列表是否包含元素e
    • int indexOf(Object o)查找列表中元素o的位置, 不存在返回-1
  • 以上方法在列表中判断的时候, 都是使用equals方法比较, 所以即使的相同值的不同实例对象也能判断出来, 而==只判断是否为同一对象
  • 在自定义的类型列表中使用以上方法, 就必须要正确的覆写自定义类的equals方法
  • 编写equals方法需要满足:
    • 自反性(Reflexive):对于非null的x来说, x.equals(x)必须返回true
    • 对称性(Symmetric):对于非null的x和y来说,如果x.equals(y)为true,则y.equals(x)也必须为true
    • 传递性(Transitive):对于非null的x、y和z来说,如果x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)也必须为true
    • 一致性(Consistent):对于非null的x和y来说,只要x和y状态不变,则x.equals(y)总是一致地返回true或者false
    • 对null的比较:即x.equals(null)永远返回false
  • 编写equals方法:
    • 先确定实例“相等”的逻辑
    • instanceof判断传入的待比较的Object是不是属于当前类型
    • 对引用类型用Objects.equals()比较(可以进行null比较),对基本类型直接用==比较
    • 注意要import java.util.Objects;
  • 不需要用到对象的相等判断时, 可以不用覆写equals

2、Map

  1. Map<K, V>是一种键-值映射表

    • put(K key, V value)
    • V get(K key), 不存在则返回null
  2. Map中的key不重复, value可以重复出现

  3. 遍历Map:

    • keySet()
    • entrySet()
    // 最常用的Map是HashMap(哈希映射表)
    Map<String, Integer> map = new HashMap<>()
    for(String s: map.keySet()) {
        Integer i = map.get(s);
    }
    for(Map.Entry<String, Integer> e: map.entrySet()) {
        String k = e.getKey();
        Integer v = e.getValue();
    }
    
  4. 自定义键值存储, 正确使用Map必须保证:

    • 作为key的对象必须正确覆写equals()方法, 相等的两个key实例调用equals()必须返回true

    • 作为key的对象还必须正确覆写hashCode()方法, 且hashCode()方法要严格遵循以下规范:

      • 如果两个对象相等, 则两个对象的hashCode()必须相等
      • 如果两个对象不相等, 则两个对象的hashCode()尽量不要相等
    • equals方法跟List中的一样, hashCode方法则可以利用所比较对象的hashCode方法来实现

    • 对于null, 与equals类似可以使用Objects.hash方法:

      public int hashCode() {
          return Objects.hash(name, score, age);
      }
      // Objects.hashCode(Object o);
      // Objects.hash(Object... o);
      
  5. HashMap是通过hash算法实现位置存储, 具有顺序不可预测性

3、EnumMap
  1. 如果Map存储的key是枚举enum类型的, 可以使用java.util.EnumMap类型, 可以节省额外的空间浪费(类似一种对应翻译)

    Map<MyEnum, String> map = new EnumMap<>(MyEnum.class);
    
4、TreeMap
  1. Map下有一个SortedMap接口, 内部会对存储的key进行排序, 它的实现类是TreeMap

  2. 使用TreeMap时, 传入的key的类需要实现Comparable接口

  3. java内部类已经实现了Comparable, 可以直接使用

  4. 如果没有实现Comparable接口, 可以在创建TreeMap时传入自定义排序算法:

    import java.util.*;
     Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
         public int compare(Person p1, Person p2) {
             return p1.name.compareTo(p2.name);
         }
     });
    
  5. TreeMap比较规则, key相等返回0, 前面的key与后面的key比较返回-1, 否则返回1

  6. 在获取数据时, TreeMap返回经上述比较后为0的key的value, 所以需要规范的定义比较规则

  7. 注意: TreeMap不依赖equalshashCode进行存储和索引

Properties
  1. java支持配置文件.properties, 使用Properties读取

  2. 配置文件每行以key=value的形式存储, #开头表示注释行

  3. Properties本质上是一种Hashtable, 但是不要使用继承的put和get方法

  4. load(InputStream)/load(Reader)

  5. setProperties(String k, String v), getProperties(String k)/getProperties(String k, String default)

  6. store(OutputStream)

     Properties pr = new Properties();
     // 直接传入指定的文件字节流
     pr.load(new java.io.FileInputStream("setting.properties"));
     // 传入相对类目录classpath的文件流
     pr.load(getClass().getResourceAsStream("/common/setting.properties"));
     // 传入内存字节流
     String setting = "# test" + "\n" + "course=Java" + "\n" + "last_open_date=2019-08-07T12:35:01";
     pr.load(new ByteArrayInputStream(setting.getBytes("utf-8")));
     // 中文编码, 传入字符流, 不涉及编码问题
     pr.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
    
     props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");
    
  7. 多次load时, 后面的内容会对前面的内容进行覆盖

  8. 获取配置不存在会返回null, 也可以在获取时设置默认返回值

4、Set

  1. Set<E>用于存储不重复元素的集合:

    • boolean add(E e);
    • boolean remove(Object e);
    • boolean contains(Object e);
    • int size();
  2. Set相当于只存储key不存储value的Map, 所以也要正确实现equals和hashCode方法

  3. Set接口最常用的实现类是HashSet, HashSet实际上是对HashMap的简单封装

  4. Map类似, Set无序存储, SortedSet为有序存储接口, 实现类为TreeSet, 按元素排序顺序存储

  5. TreeMap类似, TreeSet传入的类型需要实现Comparable接口, 或者在构建是传入自定义比较器Comparator:

    Set<MyClass> set = new TreeSet<>(new Comparator<MyClass>() {
        public int compare(MyClass mc1, MyClass mc2) {
            return Integer.compare(mc1.num, mc2.num);
            // return mc1.name.compareTo(mc2.name);
        }
    })
    

5-1、Queue(接口)

  1. 队列Queue接口实际上是一种先进先出(FIFO)的有序表:

    • int size(): 获取队列长度
    • boolean add(E e) / boolean offer(E e): 在队尾添加元素
    • E remove() / E poll(): 获取队首元素并从队列中删除
    • E element() / E peek(): 获取队首元素但不从队列中删除
    • 上面3种操作总是有两个不同的方法, 在操作失败发生异常时, 前面的方法抛出异常, 后面的方法返回false或者null
    • 注意: 不要将null添加到队列, 否则poll和peek无法判断结果
  2. LinkedList既实现了List接口, 也实现了Queue接口, 在使用时, List引用可以调用List方法, Queue引用可以调用Queue方法

     // 这是一个List:
     List<String> list = new LinkedList<>();
     // 这是一个Queue:
     Queue<String> queue = new LinkedList<>();
    
5-2、PriorityQueue
  1. PriorityQueueQueue接口的特殊实现类,相当于是有序队列, 会根据优先级决定出队顺序
  2. 放入PriorityQueue队列的元素类需要实现Comparable接口, 没有实现则可以在构建时传入Comparator接口的实现实例
5-3、Deque(接口)
  1. Deque是一种双端队列(Double Ended Queue), 在队首和队尾都可以入队或出队
  2. Deque接口继承自Queue接口, 是Queue的一种扩展
    • addLast(E e) / offerLast(E e)
    • E removeFirst() / E pollFirst()
    • E getFirst() / E peekFirst()
    • addFirst(E e) / offerFirst(E e)
    • E removeLast() / E pollLast()
    • E getLast() / E peekLast()
  3. Deque中也可以调用Queue的add和offer方法, 但最好不要使用, 应该使用addLast/offerLast
  4. Deque接口的实现类有ArrayDeque, LinkedList
  5. 同样, 对应LinkedList应该总是使用特定的接口来引用, 尽量满足面向抽象编程的规范
  6. 避免将null加入Deque
5-4、Stack(接口)
  1. 栈Stack是一种后进先出(LIFO)的数据结构
  2. 由于历史遗留问题, java没有创建独立的Stack结构, 可以使用Deque实现Stack功能:
    • 压栈:push(E)/addFirst(E)
    • 出栈:pop()/removeFirst()
    • 取栈顶元素但不弹出:peek()/peekFirst()

6、Iterator

  1. 自定义的类想用于for each循环, 就需要实现Iterable接口, 返回Iterator接口实现, 编译器自动改写:

     for(Iterator<String> it = list.iterator(); it.hasNext(); ) {
         String s = it.next();
         System.out.println(s);
     }
    
  2. Iterator是一种抽象的数据访问模型, 使用Iterator模式进行迭代的好处有:

    • 对任何集合都采用同一种访问模型
    • 调用者可以对集合内部结构一无所知
    • 集合类返回的Iterator对象知道如何迭代
  3. 通常外部类实现Iterable接口, 用一个内部类来实现Iterator接口,这个内部类可以直接访问对应的外部类的所有字段和方法:

    • 通过MyOutterClass.this内部类可以获取外部类的当前实例对象(有点像获取当前线程)
    • 内部类需要实现hasNext和next方法
    • 而外部类通常只需要返回内部类的实例

7、Collections

  1. Collections定义了一系列的静态方法, 能更方便的操作各种集合

    public static boolean addAll(Collection<? super T> c, T... elements) { ... }
    
  2. 创建空集合:

    • 创建空List: List<T> emptyList()
    • 创建空Map: Map<K, V> emptyMap()
    • 创建空Set: Set<T> emptySet()
    • 返回的空集合是不可变集合, 与of(T...)方法等价
  3. 创建单元素集合:

    • 创建一个元素的List: List<T> singletonList(T o)
    • 创建一个元素的Map: Map<K, V> singletonMap(K key, V value)
    • 创建一个元素的Set: Set<T> singletonSet(T o)
    • 返回的单元素集合也是不可变集合, 与of(T...)方法等价
  4. Collections可以对List排序:

    • Collections.sort(List l)
    • 排序会直接修改list, 所以必须传入可变List
  5. 洗牌算法:

    • Collections.shuffle(List l)
  6. 不可变集合:

    • Collections还提供了一组方法把可变集合封装成不可变集合
    • 封装成不可变List: List<T> unmodifiableList(List<? extends T> list)
    • 封装成不可变Set: Set<T> unmodifiableSet(Set<? extends T> set)
    • 封装成不可变Map: Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)
    • 这种封装实际上是通过创建一个代理对象, 拦截掉所有修改方法实现的
    • 这种方式可以继续修改原集合, 而且会影响封装后的"不可变性"
    • 可以通过将原集合的引用赋值为null的方式丢弃原引用, 从而保护其不可变性
  7. 线程安全集合:

    • 变为线程安全的List: List<T> synchronizedList(List<T> list)
    • 变为线程安全的Set: Set<T> synchronizedSet(Set<T> s)
    • 变为线程安全的Map: Map<K,V> synchronizedMap(Map<K,V> m)
    • Java 5之后引入了更高效的并发集合类(Concurrent***, 上述方法基本已不用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值