Java笔记07 - 集合

java标准库中被使用最多的类型

Java集合简介

  • 集合是为了处理一组类似的数据
  • 如果一个Java对象可以在内部持有若干个其他Java对象, 并对外提供访问接口, 就是Java中的集合

Collection

除Map外, 其他集合类的根接口

  • java.util主要提供以下三种类型的集合
    • List: 一种序列表的集合.
    • Set: 保证没有重复元素的集合.
    • Map: 通过键值(key-value)查找的映射表集合.
  • 集合的设计特点:
    • 实现了接口和继承类相分离
    • 支持泛型
    • 通过迭代器(Iterator)访问
  • 尽量不适用遗留接口

使用List

  • List: 是一种有序链表
  • List和数组几乎完全相同:
    • 按照元素的先后顺序存放
    • 每个元素都可以通过索引确定自己的位置
    • 索引从0开始
  • ArrayList内部使用数组来存储元素
    • 添加的使用, 向后挪一位
    • 继续添加, 没空闲的时候, 就再造一个更大的数组, 然后把这个数组复制过去
  • 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()
  • LinkedList通过链表也实现了List接口
  • LinkedList中, 每个元素都指向下一个元素

List特点

  • 可以添加重复元素
  • 可以添加null

创建List

  • 可以通过List.of()进行创建, 根据给定元素快速创建List
  • 不接受null

遍历List

  • 使用Iterator
  • 由List实例调用iterator()创建
  • hasNext()是否含有下一个元素
  • E next()返回下一个元素
    List<String> list = List.of("apple", "pear", "banana");
    for (Iterator<String> it = list.iterator(); it.hasNext();) {
      String s = it.next();
      System.out.println(s);
    }
  • Iterator遍历List永远都有最高效的方式
  • Java中的for each使用的就是Iterator遍历
  • 只要实现了Iterable接口的集合都可以直接使用for each进行遍历

List和Array互换

  • List to Array
    • toArray()方法直接返回一个Object[]数组: 造成信息丢失
    • toArray(T[])传入一个相同类型的Array, List自动把元素复制到传入的Array
      • 可以传入向上兼容的类型.
      • 如果传入的数组元素比之前多了, 会再创建一个数组进行存储.
    • Number[] arr = list.toArray(Number[]::new);: 函数式写法
  • Array to List
    • 使用List.of(T...)
    • List.of生成的List是只读的.
    • 调用add(), remove()方法会抛出错误.

编写equals方法

  • boolean contains(Object o)方法判断List是否包含某个指定元素.
  • int indexOf(Object o)方法可以返回某个元素的索引

编写equals

  • 编写规则:
    • 自反性: 非null的x. x.equals(x)返回true
    • 对称性: 非null的x, y. x.equals(y)为true, 那么y.equals(x)也必须为true
    • 传递行: 非null的x, y, z. x.equals(y)为true, y.equals(z)为true, x.equals(z)也必须为true
    • 一致性: 非null的x, y, 只要x和y的状态不变, x.equals(y)总是一致返回true或者false
    • 对null的比较: x.equals(null)总是返回false
  • 编写步骤:
    • 先确定实例相等逻辑
    • instanceof判断传入的实例, 是否为当前的类型
    • 对与引用类型, 使用Objects.equals()进行判断, 基本类型, 使用==
    • Objects.equals判断两个null, 他们相等.

使用Map

  • 高效通过key快速查找value(元素)
  • Map<K, V>是一种键-值映射表
  • 调用put(K key, V value)方法时, 即将keyvalue做了映射关系
  • 调用V get(K key)时, 就可以通过key, 获取value
  • key, 不存在, 返回null
  • boolean containsKey(K key)方法, 判断key是否存在
  • 不存在重复的key, 如果放入同样的key, 会把之前的key替换掉, 存一个新的value
  • value是可以重复的

遍历Map

  • .keySet()获取key的集合, 在用for each遍历集合
  • .entrySet()同时遍历keyvalue
  • Map不能保证顺序. 遍历MAP, 应该假设输出的key是无序的

编写equals和hashCode

  • hashMap内部使用空间换时间的方法
  • 作为key的对象, 必须正确覆写equals方法
  • key对象, 进行hashCode(), 返回一个int整数
  • 作为key的对象, 必须正确覆写hashCode()方法
  • 如果两个对象相等, 则hashCode()必须相等
  • 如果两个对象不相等, 则hashCode()尽量不相等
  • 可以使用Objects.hash(a, b, c)进行覆写

延伸阅读

  • HashMap默认大小只有16, int index = key.hashCode() & 0xf
  • 数组不够用了, 扩容一倍, 重写计算key
  • 频繁扩容影响性能, 所以一开始制定容量, Map<String, Integer> map = new HashMap<>(1000)
  • HashMap实际上不是直接存储的实例对象, 而是对应一个entrylist
  • 如果key.hashCode()以后相同, 就放到不同的两个entry上面.
  • 然后遍历整个list, 找到对应的entry

使用EnumMap

  • 如果keyenum对象
  • 内部使用非常紧凑的数组存储value
  • 根据enum类型的key, 直接定义到内部数组的索引, 不需要计算hashCode()
  • EnumMap时间和空间都可以保证, 优先使用
  • EnumMap使用的时候, 持有Map接口

使用TreeMap

  • SortedMap会对key进行排序. SortedMap是接口, TreeMap是实现类
  • 保证遍历时, 以key的顺序进行排序
  • key必须实现Comparable接口
  • String.compareTo()在两个值相同的时候, 返回0, 就是判断一样
  • TreeMapkey不需要实现equalshashCode

使用Properties

  • 配置文件特定: Key-Value是String-String
  • Properties表示一组配置

读取配置文件

  • 创建Properties实例
  • 调用load()读取文件
  • 调用getProperty()获取配置
  • 接受一个InputStream, 表示字节流, 所以文件字节流和jar包中的资源流都可以
  • 也可以从内存中直接读取一个字节流
  • 多个.properties文件, 反复load()读取, 后面读取的key-value会覆盖已经读取的
  • 历史问题, 从Hashtable派生, 只使用getPropertysetProperty, 不适用继承的get()put()方法

写入配置文件

  • setProperty()修改属性, 使用store()进行存储

编码

  • load(InputStream)默认总是以ASCII编码, 所以会导致读到乱码
  • load(Reader)方法解决, 一个字节流, 一个字符流, 字符在内容已经char类型表示了, 不涉及编码问题

使用Set

  • 存储不重复的key, 不需要存储映射的value, 使用Set
  • 添加: boolean add(E e)
  • 删除: boolean remove(Object e)
  • 判断是否含有: boolean contains(Object e)
  • Set是只存key, 不存value的map
  • Set中的元素需要实现equals()hashCode()
  • HashSet仅仅是对HashMap的一个简单封装
  • Set接口不能保证有序, 但是SortedSet接口可以保证有序
  • TreeSet可以保证有序, 元素正确实现Comparable接口

使用Queue

  • 队列只有两个操作:
    • 把元素添加到队列末尾
    • 从队列头部取出元素
  • 方法:
    • int size(): 获取队列长度
    • boolean add(E)/boolean offer(E)添加元素到队尾
    • E remove() / E poll()获取首元素并从队列中删除
    • E element() / E peek()获取首元素但不从队列中删除
  • 区别:
    • add()添加失败, 抛出异常 / offer()添加失败, 返回false
    • 如果空队列: remove(), 抛出异常 / pull(), 返回null 所以不要把null放到队列
    • pull()获取并删除 / peek()获取不删除, 重复使用
  • LinkedList即实现了List接口, 又实现了Queue接口.

使用PriorityQueue

  • 出队顺序和元素的优先级有关.
  • 调用remove()或者poll()方法, 返回的总是优先级最高的元素
  • 放入其中的元素必须实现Comparable接口, 从而根据优先级进行出队

使用Deque

  • 双端队列, 允许两头进, 两头出.
  • 可以添加元素到队尾, 也可以添加到队首
  • 可以从队尾获取, 也可以从队首获取
  • addLast() / addFirst()
  • 扩展自Queue, 但最好不要使用add()等方法
  • Deque的实现类ArrayListLinkedList
  • 面向对象编程的原则: 尽量持有接口, 而不是具体的实现类

使用Stack

  • 栈: 是一种后进先出的数据结构
  • 压栈: push(E) / addFirst(E)
  • 栈顶元素弹出: E pop() / E removeFirst()
  • 取栈顶元素但不弹出E peek() / E peekFirst()
  • 没有Stack接口, 使用Deque进行模拟, 只使用push, pop, peek方法

Stack的作用

  • jvm使用栈结构维护方法的调用顺序

    调用的时候, 会先把参数压栈, 然后执行对应的方法; 方法返回时, 返回值压栈, 调用方法通过出栈操作获得方法返回值.

  • 调用方法有容量现在, 调用过多会造成栈溢出: StackOverflowError

  • 对整数进行进制转换

  • 计算表达式后缀

使用iterator

  • Java的集合类都可以使用for each循环
      for (Iterator<String> it = list.iterator();it.hasNext();) {
        String s = it.next();
        System.out.println(s);
      }
  • Iterator对象是在内部创建, 对象知道如果高效处理自己的结构
  • 调用方只关心统一的方法调用
  • 实现要求:
    • 集合类实现了Iterable接口, 要求返回一个Iterator对象
    • Iterator对象迭代集合内部数据
import java.util.*;
public class Main {
    public static void main(String[] args) {
      ReverseList<String> rList = new ReverseList();
      rList.add("A");
      rList.add("B");
      rList.add("C");
      for (String s : rList) {
        System.out.println(s);
      }
    }
}

class ReverseList<T> implements Iterable<T> {
  private List<T> list = new ArrayList<>();
  public void add(T t) {
    list.add(t);
  }
  
  @Override
  public Iterator<T> iterator() {
    return new ReverseIterator(list.size());
  }
  
  class ReverseIterator implements Iterator<T> {
    int index;
    ReverseIterator(int index) {
      this.index = index;
    }
    @Override
    public boolean hasNext() {
      return index > 0;
    }
    public T next() {
      index--;
      return ReverseList.this.list.get(index);
    }
  }
}

  • 通过内部类实现Iterator接口

  • 这个内部类, 可以直接访问对应外部类的全部字段和方法

  • 使用外部类.this获取

  • 集合类实现了Iterable接口, 提供iterator方法, 返回Iterator实例

使用Collections

  • Collections工具类, 提供一系列操作集合的方法

创建空集合

  • 创建空List: List<T> emptyList()
  • 创建空Map: Map<T> emptyMap()
  • 创建空Set: Set<T> emptySet()
  • .of()相同: 空集合是不可变集合, 无法添加或者删除元素.
    List<String> list1 = List.of();
    List<String> list2 = Collections.emptyList();

创建单元素集合

  • List<T> singletonList(T t)

  • Map<K, V> singletonMap(K k, V v)

  • Set<T> singleton(T t)

  • 返回的单元素集合, 或者多个元素集合, 也是不可变的, 还是.of()方便

排序

  • Collections.sort()进行排序, 修改List中元素的位置

洗牌

  • Collections.shuffle()进行洗牌, 打乱元素位置

不可变集合

  • 提供一系列方法, 将集合变成不可变的
  • 不可变List: List<T> uList = Collections.unmodifiableList(list);
  • Set, Map一个样
  • 其实就是拦截修改方法
  • 但是可以操作原来的数组, 所以最好是马上放弃原来的索引

线程安全集合

  • List<T> synchronizedList(List<T> list)
  • 后面设计度线程, java5开始引入更高效并发类, 线程安全集合没有什么用了.

疑问

  • 文件流的文件位置始终不对.
posted @ 2020-04-11 11:35  张润昊  阅读(177)  评论(0编辑  收藏  举报