java-集合

集合简介

Collection 接口

Collection,它是除Map外所有其他集合类的根接口。

package java.util;

public interface Collection<E> extends Iterable<E>

Iterable 接口

package java.lang;

public interface Iterable<T> {
    // 获取迭代器
    Iterator<T> iterator();
    
    // 遍历元素
    default void forEach(Consumer<? super T> action) {...}
    
    // 获取可分割迭代器
    default Spliterator<T> spliterator() {...}
}

Iterator 接口

Java访问集合总是通过统一的方式——迭代器(Iterator)来实现。

package java.util;

public interface Iterator<E>

例子:

List<Number> list = new ArrayList<>();
list.add(1);
list.add(2);

Iterator<Number> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
    iterator.remove();
}
System.out.println(list);//[]

Spliterator接口

本质:与Iterator差不多,只是Spliterator可以拆分成多份去遍历,用于并行遍历。

方法签名为Spliterator spliterator(),该方法返回容器的可拆分迭代器。从名字来看该方法跟iterator()方法有点像,我们知道Iterator是用来迭代容器的,Spliterator也有类似作用,但二者有如下不同:

  1. Spliterator既可以像Iterator那样逐个迭代,也可以批量迭代。批量迭代可以降低迭代的开销。
  2. Spliterator是可拆分的,一个Spliterator可以通过调用Spliterator trySplit()方法来尝试分成两个。一个是this,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。

可通过(多次)调用Spliterator.trySplit()方法来分解负载,以便多线程处理。

参考:原理jdk8中Spliterator的作用 - facelessvoidwang - 博客园 (cnblogs.com)

使用Spliterator:

import java.util.ArrayList;
import java.util.Spliterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            list.add(i);
        }

        // 数据平均分配到四个线程
        Spliterator<Integer> spliterator01 = list.spliterator(); //01中有20个元素
        Spliterator<Integer> spliterator02 = spliterator01.trySplit(); //01中有10个元素,02中有10个元素
        Spliterator<Integer> spliterator03 = spliterator01.trySplit(); //01中有5个元素,02中有10个元素,03中有5个元素
        Spliterator<Integer> spliterator04 = spliterator02.trySplit(); //01中有5个元素,02中有5个元素,03中有5个元素,04中有5个元素

        MyThread t01 = new MyThread(spliterator01);
        MyThread t02 = new MyThread(spliterator02);
        MyThread t03 = new MyThread(spliterator03);
        MyThread t04 = new MyThread(spliterator04);

        t01.start();
        t02.start();
        t03.start();
        t04.start();
    }
}

//处理线程
class MyThread extends Thread {
    // 数据源
    private Spliterator<Integer> spliterator;

    public MyThread(Spliterator<Integer> spliterator) {
        this.spliterator = spliterator;
    }

    @Override
    public void run() {
        spliterator.forEachRemaining(x -> System.out.println(Thread.currentThread().getName() + " === " + x));
    }
}

输出:

Thread-2 === 10
Thread-2 === 11
Thread-2 === 12
Thread-2 === 13
Thread-2 === 14
Thread-3 === 0
Thread-3 === 1
Thread-3 === 2
Thread-3 === 3
Thread-3 === 4
Thread-0 === 15
Thread-0 === 16
Thread-0 === 17
Thread-0 === 18
Thread-0 === 19
Thread-1 === 5
Thread-1 === 6
Thread-1 === 7
Thread-1 === 8
Thread-1 === 9

遗留类

由于Java的集合设计非常久远,中间经历过大规模改进,我们要注意到有一小部分集合类是遗留类,不应该继续使用:

  • Hashtable:一种线程安全的Map实现;
  • Vector:一种线程安全的List实现;
  • Stack:基于Vector实现的LIFO的栈。

还有一小部分接口是遗留接口,也不应该继续使用:

  • Enumeration:已被Iterator取代。

List

源码

public interface List<E> extends Collection<E>

常用方法:

// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();//转为数组,会新分配数组
<T> T[] toArray(T[] a);//转为数组,如果a容量不够,则会新分配数组

// Modification Operations
boolean add(E e);
boolean remove(Object o);//删除某个元素

// Bulk Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);//从指定位置开始插入
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);//保留c中的元素,删除其他的
default void replaceAll(UnaryOperator<E> operator) {}//对所有元素执行替换操作
default void sort(Comparator<? super E> c) {}//排序
void clear();

// 比较与哈希
boolean equals(Object o);
int hashCode();

// 位置访问操作
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);

// 查找操作
int indexOf(Object o);
int lastIndexOf(Object o);

// List Iterators
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);

// View
List<E> subList(int fromIndex, int toIndex);
default Spliterator<E> spliterator() {}//获取可分割迭代器

List和Array转换

【List 转 Array】

(1)调用toArray()方法直接返回一个Object[]数组:

List<Integer> list = new ArrayList<>();
Object[] array1 = list.toArray();//ok
//因为ArrayList内部数组是Object[]类型,所以toArray()返回值本身是Object[]类型
Integer[] array2 = (Integer[]) list.toArray();//error: ClassCastException

这种方法会丢失类型信息,所以实际应用很少。

(2)给toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array中:

List<Integer> list = new ArrayList<>();
Integer[] array2 = list.toArray(new Integer[0]);//ok,根据入参数组决定数组类型
Number[] array3 = list.toArray(new Number[0]);//ok

【Array 转 List】

Integer[] array = {1, 2, 3};
List<Integer> list1 = Arrays.asList(array);
List<String> list2 = Arrays.asList("1", "2");

Map

源码

public interface Map<K,V>

常用方法:

// Query Operations
int size();//键值对数量,如果超过 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);//获取value,不存在则返回 null

// Modification Operations
V put(K key, V value);//更新KV对,返回旧 value
V remove(Object key);//删除KV对,返回 value

// Bulk Operations
void putAll(Map<? extends K, ? extends V> m);
void clear();

// Views
Set<K> keySet();//返回键的视图,它和原 map 相互影响
Collection<V> values();//返回值的视图,它和原 map 相互影响
Set<Map.Entry<K, V>> entrySet();//返回KV视图,它和原 map 相互影响

// Comparison and hashing
boolean equals(Object o);
int hashCode();

// 默认实现的方法
default V getOrDefault(Object key, V defaultValue) {}
default void forEach(BiConsumer<? super K, ? super V> action) {}
//所有元素执行替换操作
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {}
//如果键不存在或值为null(更新value,返回null);如果键存在(返回旧值)
default V putIfAbsent(K key, V value) {}
//当(key,value)存在时,删除对应键值对
default boolean remove(Object key, Object value) {}
//当(key,oldValue)存在时,更新值
default boolean replace(K key, V oldValue, V newValue) {}
//如果key存在,更新值,返回旧值
default V replace(K key, V value) {}
//大意:如果key不存在,则根据key计算出newValue,插入该键值对
//如果key不存在或值为null,且mappingFunction(key)结果不为null,则插入新键值对。
//返回当前value(本来存在的 或者 计算出来的)
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {}
//大意:如果key存在,则根据(key,oldValue)计算出newValue,更新该键值对
//(1)如果key存在,且oldValue不为null,则计算 newValue = mappingFunction(key, oldValue)
//(1.1)如果newValue不为null则更新值,返回newValue
//(1.2)如果newValue为null则删除值,返回null
//(2)如果key不存在,或oldValue为null,则返回null
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {}
//大意:根据(key,oldValue)计算出newValue,更新该键值对
//得到(key, oldValue),计算 newValue = remappingFunction(key, oldValue)
//(1)如果 newValue 为 null,则删除该 key,返回 null
//(2)如果 newValue 不为 null,更新键值对,返回 newValue
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {}
//大意:合并新旧value(merge是特殊的compute)
//找到oldValue,计算 newValue = (oldValue == null) ? value : remappingFunction(oldValue, value)
//如果newValue为null,则删除key;如果newValue不为null,则更新key
//返回newValue
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {}

Map.Entry<K, V> 接口:

K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
//返回比较器,比较的类型是Map.Entry<K, V>,比较 key 值
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {}
//返回比较器,比较的类型是Map.Entry<K, V>,比较 value 值
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {}
//返回比较器,比较的类型是Map.Entry<K, V>,比较 key 值,比较规则由入参决定
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {}
//返回比较器,比较的类型是Map.Entry<K, V>,比较 value 值,比较规则由入参决定
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {

遍历Map

Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 456);
map.put("banana", 789);

//遍历 key
for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println(key + " = " + value);
}

//遍历 key 和 value
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " = " + value);
}

HashMap查找key原理

根据key的hashCode确定KV对存储位置(即数组下标),但是这个位置下面可能有多个KV对,遍历这多个KV对,key与每个键用equals()比较,为true则找到KV对。

EnumMap

如果Map的key是enum类型,推荐使用EnumMap,既保证速度,也不浪费空间(因为它的key范围是固定的,所以内部数组是紧凑的,并且根据 key 直接定位到内部数组的索引,并不需要计算 hashCode())

import java.time.DayOfWeek;
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
        map.put(DayOfWeek.MONDAY, "星期一");
        map.put(DayOfWeek.TUESDAY, "星期二");
        map.put(DayOfWeek.WEDNESDAY, "星期三");
        map.put(DayOfWeek.THURSDAY, "星期四");
        map.put(DayOfWeek.FRIDAY, "星期五");
        map.put(DayOfWeek.SATURDAY, "星期六");
        map.put(DayOfWeek.SUNDAY, "星期日");
        System.out.println(map);
        System.out.println(map.get(DayOfWeek.MONDAY));
    }
}

TreeMap

(1)TreeMap:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

NavigableMap:

public interface NavigableMap<K,V> extends SortedMap<K,V>

SortedMap:

public interface SortedMap<K,V> extends Map<K,V>

(2)关于Key的要求:

使用TreeMap时,放入的Key必须实现Comparable接口。

如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法:

public class Main {
    public static void main(String[] args) {
        Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
            public int compare(Person p1, Person p2) {
                return p1.name.compareTo(p2.name);
            }
        });
        map.put(new Person("Tom"), 1);
        map.put(new Person("Bob"), 2);
        map.put(new Person("Lily"), 3);
        for (Person key : map.keySet()) {
            System.out.println(key);
        }
        // {Person: Bob}, {Person: Lily}, {Person: Tom}
        System.out.println(map.get(new Person("Bob"))); // 2
    }
}

class Person {
    public String name;
    Person(String name) {
        this.name = name;
    }
    public String toString() {
        return "{Person: " + name + "}";
    }
}

使用Properties

因为配置文件非常常用,所以Java集合库提供了一个Properties来表示一组“配置”。由于历史遗留原因,Properties内部本质上是一个Hashtable,但我们只需要用到Properties自身关于读写配置的接口。

例子:

读取的配置文件 setting.properties:

# setting.properties

last_open_file=/data/hello.txt
auto_save_interval=60是

代码:

//1.创建 Properties 实例
Properties props = new Properties();
//2.读取配置文件
props.load(new InputStreamReader(new FileInputStream("src/setting.properties"), StandardCharsets.UTF_8));
//3.获取配置
String filepath = props.getProperty("last_open_file");//不存在返回null
String interval = props.getProperty("auto_save_interval", "120");//提供默认值
System.out.println(interval);

//4.写入配置
props.setProperty("language", "java语言");
//5.写入配置文件
props.store(new OutputStreamWriter(new FileOutputStream("src/save.properties"), StandardCharsets.UTF_8), "这是写入的properties注释");

写入的配置文件 save.properties:

#\u8FD9\u662F\u5199\u5165\u7684properties\u6CE8\u91CA
#Tue Jan 04 13:41:35 CST 2022
auto_save_interval=60是
last_open_file=/data/hello.txt
language=java语言

Set

例子

Set<String> set = new HashSet<>();

set.add("one");//true
set.add("two");//true
set.add("two");//false,添加失败,因为元素已存在

boolean isExist = set.contains("one");//true,元素存在

set.remove("three");//false,删除失败,因为元素不存在

int size = set.size();//2,一共两个元素

源码

public interface Set<E> extends Collection<E>

常用方法:

// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);

// Modification Operations
boolean add(E e);//增加成功返回true;如果已存在返回false
boolean remove(Object o);

// Bulk Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);//只要有变化就返回true
boolean retainAll(Collection<?> c);//保留c中的元素,删除其他的
boolean removeAll(Collection<?> c);
void clear();

// Comparison and hashing
boolean equals(Object o);
int hashCode();

最常用的Set实现类是HashSet,实际上,HashSet仅仅是对HashMap的一个简单封装。

有序Set

Set接口并不保证有序,而SortedSet接口则保证元素是有序的:

  • HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
  • TreeSet是有序的,因为它实现了SortedSet接口。

Queue

队列操作:队尾插入,队头获取。

源码

public interface Queue<E> extends Collection<E>

常用方法:

boolean add(E e);//插入元素,成功返回true,失败抛异常(可能超过了队列的容量)
boolean offer(E e);//插入元素,成功返回true,失败返回false

E remove();//删除队头元素并返回,队列为空时抛异常
E poll();//删除队头元素并返回,队列为空时返回null

E element();//查看队头元素,队列为空时抛异常
E peek();//查看队头元素,队列为空时返回null

PriorityQueue

(1)PriorityQueueQueue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()poll()方法,返回的总是优先级最高的元素。

(2)使用方式1:元素实现Comparable

//元素实现Comparable
Queue<String> queue = new PriorityQueue<>();
queue.offer("b");
queue.offer("a");
System.out.println(queue);//[a, b]

(3)使用方式2:实例化PriorityQueue时,提供一个Comparator对象

//实例化PriorityQueue时,提供一个Comparator对象
Queue<User> queue = new PriorityQueue<>(new UserComparator());
queue.offer(new User("A1", "Bob"));
queue.offer(new User("A2", "Alice"));
queue.offer(new User("V1", "Boss"));
System.out.println(queue.poll()); // Boss/V1
System.out.println(queue.poll()); // Bob/A1
System.out.println(queue.poll()); // Alice/A2
System.out.println(queue.poll()); // null,因为队列为空

Deque

(1)允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue)。

public interface Deque<E> extends Queue<E>

(2)常用方法:

失败抛异常失败返回false或null
插入队首void addFirst(E e)boolean offerFirst(E e)
插入队尾void addLast(E e)boolean offLast(E e)
取队首元素并删除E removeFirst()E pollFirst()
取队尾元素并删除E removeLast()E pollLast()
查看队首元素E getFirst()E peekFirst()
查看队尾元素E getLast()E peekLast()
boolean removeFirstOccurance(Object o);
boolean removeLastOccurrence(Object o);

// *** Stack methods ***
void push(E e);//入栈,等于 addFirst(e)
E pop();//出栈,等于 removeFirst()

(3)例子:

Deque<String> deque = new LinkedList<>();
deque.offerLast("A"); // A
deque.offerLast("B"); // A <- B
deque.offerFirst("C"); // C <- A <- B
System.out.println(deque.pollFirst()); // C, 剩下A <- B
System.out.println(deque.pollLast()); // B, 剩下A
System.out.println(deque.pollFirst()); // A
System.out.println(deque.pollFirst()); // null

Collections工具类

创建集合

(1)创建空集合:

  • 创建空List:List emptyList()
  • 创建空Map:Map emptyMap()
  • 创建空Set:Set emptySet()

要注意到返回的空集合是不可变集合,无法向其中添加或删除元素。

(2)创建一个单元素集合:

  • 创建一个元素的List:List singletonList(T o)
  • 创建一个元素的Map:Map singletonMap(K key, V value)
  • 创建一个元素的Set:Set singleton(T o)

要注意到返回的单元素集合也是不可变集合,无法向其中添加或删除元素。

(3)把可变集合封装成不可变集合:

  • 封装成不可变List:List unmodifiableList(List list)
  • 封装成不可变Set:Set unmodifiableSet(Set set)
  • 封装成不可变Map:Map unmodifiableMap(Map m)

这种封装实际上是通过创建一个代理对象,拦截掉所有修改方法实现的。

排序

public static <T extends Comparable<? super T>> void sort(List<T> list) {}

洗牌

public static void shuffle(List<?> list) {}

CheckedList

如果列表没有泛型信息,则可以放入不同类型的元素:

List list = new ArrayList<>();
list.add(1);
list.add("two");
System.out.println(list);//[1, two]

使用CheckedList,如果插入的元素类型错误,将立即导致ClassCastException:

List list = new ArrayList<>();
list.add(1);
List tsafelist = Collections.checkedList(list, Integer.class);
tsafelist.add("");//异常 ClassCastException
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值