Java集合

Collection 接口

Java类中,集合类的基本接口是Collection接口,有两个基本方法:

public interface Collection<E>{
    boolean add(E element);
    Iterator<E> iterator();
    ...
}

除了这两个方法外还有其他方法,稍后写到。

add方法用于向集合内添加元素,如果集合发生了变化则返回true,没有发生变化则返回false;集合中不允许相同元素,所以如果添加一个集合中已有的元素,那么就会添加失败

iterator 方法用来返回一个实现了Iterator 接口的对象,可以用这个迭代器对象依次访问集合中的元素

迭代器

Iterator 接口包含四个方法:

public interface Iterator{
    E next();
    boolean hasNext();
    void remove;
    default void forEachRemaining(Consume<? super E> action);
}

通过反复调用next() 方法可以逐个访问集合中的每一个元素,但是如果到了结尾还在访问则会抛出NoSuchElementException。因此在访问之前先用hasNext判断是否有下一个next节点。

循环访问:

  • Collection 接口实现了Iterable接口,因此对于标准库内的任何集合都能够用for each循环,编译器会将for each循环转换为带有迭代器的循环

  • iterator 接口中的forEachRemaining 方法访问,这个方法最简单的用法就是调用lambda 表达式:

    iterator.forEachRemaining(e->System.out.println(e));
    

Iterator 接口的remove 方法会删除上次调用next 方法时返回的元素,也就是说,在使用remove方法前必须先使用next 方法。如果remove前没有调用next则会抛出IllegalStateException异常

API:

java.util.Collection

  • Iterator iterator()
    返回一个用于访问集合中各元素的迭代器

  • int size()
    返回当前存储在集合中的元素个数

  • boolean isEmpty()
    如果集合为空返回true

  • boolean contains(Object obj)
    如果集合中包含一个与obj相等的对象则返回true

  • boolean containsAll(Collection other)
    如果集合中包含other中的所有元素,则返回true

  • boolean add(E e)
    向集合中添加元素,如果成功则返回true

  • boolean addAll(Collection e)
    将e集合中的所有元素添加到集合中,如果原集合发生改变则返回true

  • boolean remove(Object obj)
    删除一个匹配到的对象

  • boolean removeAll(Collection e)
    删除原集合中所有与e集合匹配的元素

  • default boolean removeIf(Predicate<? extends String> filter)
    删除集合中filter返回true的所有元素,可以用lambda表达式,下例删除所有值为”S“的元素

    link.removeIf(e->{
    	return e.equals("S");
    })
    
  • void clear()
    删除集合中的所有元素

  • retainAll(Collection other)
    删除原集合中所有other中没有的元素

  • Object[] toArray()
    返回这个集合对象的数组

  • T[] toArray(T[] array)
    如果array数组足够大,那么用集合的元素填充array,array剩余的空间填充null;如果不够大,分配一个新数组,成员类型与array相同,长度为集合的大小,并填充集合的元素,最后返回这个数组

java.util.Iterator

  • boolean hasNext()
    如果存在另一个可访问的元素则返回true

  • E next()
    返回将要访问的下一个对象

  • void remove()
    返回上一次访问的元素

  • default void forEachRemaining(Consume<? super E> action)
    访问元素,并传递到指定的动作,可以用lambda表达式

    iterator.forEachRemaining(action->{
        System.out.println(action);
    })
    

    如果只需要输出,那么利用方法引用

    iterator.forEachRemaining(System.out::println);
    

集合框架中的集合

带List、Set、Queue 的集合都实现了Collection接口,可以用上述Collection中的方法对这些集合进行操作;

Collection 的派生接口有

  • List
  • Set => SortedSet => NavigableSet
  • Queue => Deque

Map 的派生接口有

  • SortedMap => NavigableMap

Iterator 的派生接口有

  • ListIterator

链表

LinkedList

实现了Collection 接口,底层用双向链表实现

构造一个链表

  • **LinkedList() **:构造一个空链表
var link=new LinkedList<String>();
  • **LinkedList(Collection<? extends E> e) **:构造一个链表,并添加e集合中的所有元素
var test=new LinkedList<>();
test.add("ABC");
test.add("CDE");
var linkedList=new LinkedList<>(test);

添加一个元素

  • void add(int index,E e) 在指定位置添加一个元素

    linkedList.add(1,"Hello");
    
  • void addFirst(E e) 在链表头添加一个元素 *

    linkedList.addFirst("Head");
    
  • void addLast(E e) 在链表尾添加一个元素 *

    linkedList.addLast("Tail");
    

还有 addAll 就是上面Collection 中的addAll方法

删除一个元素

  • remove() 移除当前位置的元素

    linkedList.remove();
    
  • remove(Object obj) 移除与obj相等的元素

  • remove(int index) 移除指定位置的元素

    linkedList.remove(1);linkedList.remove("a");
    
  • E removeFirst() 移除第一个元素,并返回移除的元素 *

  • E removeLast() 移除最后一个元素,并返回移除的元素 *

    linkedList.removeFirst();linkedList.removeLast();
    

还有一个 removeIf 也是和Collection 实现相同的功能

访问一个元素

  • E get(int index) 访问第i个元素,因为链表不能随机访问,所以只能遍历至第i个,时间复杂度为O(n)
  • E getFirs() 获取第一个元素
  • E getLast() 获取最后一个元素

遍历

对于链表有个专门的迭代器:ListIterator,这个迭代器实现了Iterator接口,但是扩展了 add()方法,而且有previous可以访问前一个节点,也就是说ListIterator 可以正向和反向遍历链表

ListIterator 方法有:

  • boolean hasNext() 如果有下一个元素则返回true
  • boolean hasPrevious() 如果有上一个元素则返回true
  • E next() 返回下一个元素
  • E previous() 返回前一个元素
var iter=linkedList.listIterator();		//LinkedList内置了这个用于返回ListIterator对象的方法
while(iter.hasNext()){
    System.out.println(iter.next());
}

散列集 HashSet

链表虽然按照次序存储,但是查找的时候却需要遍历查找。如果对于数据的次序没有要求,却需要很快的找到某个元素,那么就要用到散列集。散列集合没有按照添加次序存储,但是查找的时候直接查找根据散列码查找,非常快。

创建

  • HashSet() 创建一个空集
  • HashSet(Collection<? extends E> e) 创建一个集合,将e集合的元素全部添加进去
  • HashSet(int initialCapacity,float loadFactor) 指定容量和装填因子,装填因子在0.0~1.0之间。默认值是0.75,也就是说当集合中的元素达到了容量的75%,那么就会自动扩充(再散列)至原来容量的两倍

添加、移除

HashSet实现了Collection接口,添加和移除都与上面介绍Collection的一致。

HashSet没有供访问的方法,只有一个迭代器(就是Collection那个Iterator)可以用来遍历。

主要用于查找

  • boolean contains(Object o) 如果散列集中包含这个对象则返回true

这个方法经过了重写,专门用于散列的查找,众所周知散列查找非常块。

代码块:

var hash=new HashSet<Character >();
try (var file = new FileInputStream("C:\\temp.txt")) {
    int x;
    while((x=file.read())!=-1){
        hash.add((char)x);
    }
} catch (IOException e) {
    e.printStackTrace();
}
if(hash.contains('x')){
    System.out.println("There is an 'x' in the HashSet.");
}else {
    System.out.println("Can't found an 'x' in HashSet.");
}

树集 TreeSet

树集与散列集十分相似,不过树集是一个有序集合(sorted collection),添加的元素会自动进行排序。这是使用树数据结构(目前使用的是红黑树(red-black-tree)),新数据都会放在正确的排序位置,所以使用迭代器能够有序访问每一个元素。

在树集中添加元素比散列集要慢,但是与检查数组或者链表相比要快很多。查找元素时间复杂度大概是O(logn) 即为红黑树的查找时间复杂度。

在使用树集的时候必须要在类中实现Comparable接口或者构造的时候传递一个Comparator,因为树集需要有排序的规则(比如说传递的是自定义类的对象,树集是无法自行判断如何排序的)

构造一个树集

  • TreeSet() 构造一个空树集
  • TreeSet(Comparator<? super E> com) 构造一个根据com比较器排序的树集
  • TreeSet(Collection<? extends E> e) 构造一个树集,并添加集合中的所有元素
  • TreeSet(SortedSet s) 构造一个树集,添加有序集合中的所有元素,s集合与树集排序方式须一致

获取元素

  • Comparator<? super E> comparator() 获取树集的比较器
  • E first()
    E last() 返回最大值或者最小值
  • E higher(E value)
    E lower(E value) 返回大于value的最小元素或者小于value的最大元素
  • E ceiling(E value)
    E floor(E value) 返回大于等于value的最小元素或者小于等于value的最大元素
  • E pollFirst()
    E pollLast() 删除并返回这个集合中的最大元素或者最小元素,空集时返回null
  • Iterator descendingIterator 返回一个与比较器相反的排序方式的迭代器(比较器是升序则返回降序迭代器,比较器是降序则返回升序的迭代器,但是这个英文翻译是”降序迭代器“,我特意写了代码测试就是上面的结论)

中间几种访问的方法,描述的时候是用”或者“(返回最大值或者最小值),是因为这些方法是根据比较器不同而返回不同的元素。举个栗子:假如比较器是升序,那么first就是最小值,如果是降序那么first就成为最大值了。

代码块

class Employee implements Comparable<Employee>{
    private int salary;
    private String name;
    private int id;

    //构造一个比较器,第一关键字为salary,第二关键字为id,升序
    public int compareTo(Employee employee){
        if(Integer.compare(this.getSalary(),employee.getSalary())!=0){
            return Integer.compare(this.getSalary(),employee.getSalary());
        }else {
            return Integer.compare(this.getId(),employee.getId());
        }
    }

    public Employee(){}

    public Employee(String name, int id, int salary) {
        this.name=name;
        this.id=id;
        this.salary=salary;
    }

    public String getName() {        return name;    }

    public int getId() {        return id;    }

    public int getSalary() {        return salary;    }

    public void print(){
        System.out.println(this.getName()+" "+this.getId()+" "+this.getSalary());
    }

}

public class Main {
    public static void main(String[] args) {
        var tree=new TreeSet<Employee>();
        Employee[] employees=new Employee[10];
        for(int i=0;i<10;i++){
            //添加的值只是信手涂鸦
            employees[i]=new Employee(String.valueOf((i+5)*19*5/3+6),i*1000+i*18+5,50000+(i+2)*80);	
            tree.add(employees[i]);
        }
        //返回一个迭代器,与上面说的相同,比较器是升序,而这里对应的降序输出
        Iterator<Employee> iterator=tree.descendingIterator();
        while(iterator.hasNext()){
            Employee temp=iterator.next();
            System.out.println(temp.getName()+" "+temp.getId()+" "+temp.getSalary());
        }
    }
}

队列与双端队列

队列允许在尾部高效地添加或删除元素,双端队列(deque)允许在尾部和首部高效地添加或删除元素。不支持在队列中间添加或者删除元素。ArrayDeque和LinkedList都实现了Deque,但是LinkedList作为链表是可以在中间添加或删除元素的,ArrayDeque才是真正的双端队列,只能在两端操作数据。PriorityQueue(优先队列)实现了Queue接口,只能在尾部添加或删除元素

ArrayDeque

ArrayDeque 实现了Deque,可以在头部和尾部高效地添加或删除元素,但是不能处理队列中间的元素

  • void addFirst(E e)
    void addLast(E e) 在头部或尾部添加元素,如果队列满了则抛出IllegalStateException
  • boolean offerFirst(E e)
    boolean offerLast(E e) 在头部或尾部添加元素,如果队列满了则返回false
  • E removeFirst()
    E removeLast() 删除并返回队头或队尾元素,如果为空则抛出NoSuchElementException
  • E pollFirst()
    E pollLast() 删除并返回队头或队尾元素,如果为空则返回null
  • E getFirst()
    E getLast() 返回但不删除头部或尾部的元素,如果为空则抛出NoSuchElementException
  • E peekFirst()
    E peekLast() 返回但不删除头部或尾部的元素,如果为空则返回null
public class Main {
    public static void main(String[] args) {
        var q = new ArrayDeque<String>();
        q.add("ABC");
        q.add("abc");
        q.add("BCD");
        q.add("CDE");

        while (q.size()!=0){
            System.out.println(q.pollFirst() + " " + q.pollLast());
        }
    }
}
PriorityQueue

PriorityQueue实现了Queue接口,只能在队列尾部添加或删除元素,优先队列采用了”堆“数据结构。优先队列可以按照任意顺序存放,但读取的时候是按照有序的顺序读取的,与TreeSet一样,既可以存储实现了Comparable接口的对象,也能够在定义时传递Comparator对象。

  • boolean add(E e) 在队尾添加元素,如果队满则抛出IllegalStateException
  • boolean offer(E e) 在对位添加元素,如果队满则返回false
  • E remove() 删除并返回队头元素,队空则抛出NoSuchElementException
  • E poll() 删除并返回队头元素,队空则返回null
  • E element() 返回但不删除队头元素,如果队空则抛出异常
  • E peek() 返回但不删除队头元素,如果队空则返回null

如果比较器是升序,那么队尾是最小值(只能访问队尾元素);如果是降序,那么队尾是最大值。默认升序

public class Main {
    public static void main(String[] args) {

        var q=new PriorityQueue<String>((x,y)->y.compareTo(x));
        q.add("ABC");
        q.add("abc");
        q.add("BCD");
        q.add("CDE");
        while (q.size()!=0){
            System.out.println(q.poll());
        }
    }
}

比较器用了lambda表达式,这里表示降序,String按照字典序排序,所以结果为:

abc->CDE->BCD->ABC

映射

如果需要根据一个元素的精准信息查找这个元素的其他信息(如:根据身份证号码查询人名、出生日期等等),就需要用到映射。映射存储必须按照”键/值“对存储

映射有两个通用的实现:HashMap和TreeMap,HashMap对键值进行散列,TreeMap根据键值建立搜索二叉树。散列或比较函数只应用于键。映射中键与值必须一一对应,如果添加的键/值已经存在于映射中,那么新值会替换旧值(put方法替换时会返回旧值)

基本操作

往映射中添加一个值的时候,必须提供一个键 V put(K key,V value)

var hash=new HashMap<String,String>();
hash.put("10001","Mike");

根据键获取关联的值 E get()

System.out.println(hash.get("10001"));

如果映射中没有与给定键关联的值,那么get方法会返回null。如果不想要null就可以用**getOrDefault(key,value)**方法,

有关联的值则返回值,没有关联的值则返回指定的value:

System.out.println(hash.getOrDefault("10002","Oh No"));
//如果10002有关联的值,则返回关联的值,如果没有则返回"Oh No"

判断映射中是否存在某个键或值

  • boolean containsKey(Object key) 如果存在key键则返回true
  • boolean containsValue(Object value) 如果存在value值则返回true

Map接口有一个遍历方法 forEach(BiConsumer<? super K,? superV> action),将K、V传给action,这里很适合用lambda表达式:

hash.forEach((k,v)->System.out.println(k+" "+v));
更新映射条目

假设利用映射统计单词出现的频率:键是单词,值是单词出现的次数

单词出现一次则对应的值加1:

hash.put(word,hash.get(word)+1);

问题是如果word还没有出现过,那么hash.get(word)会返回一个null

  1. 使用getOrDefault() 方法,如果尚未关联则返回一个0

    hash.put(word,getOrDefault(word,0)+1);
    
  2. 使用putIfAbsent(key,value)方法,如果key没有关联值,则key关联value,已经关联了则不变

    hash.putIfAbsent(word,0);
    hash.put(word,hash.get(word)+1);
    
  3. 使用merge()

    hash.merge(word,1,Integer::sum);
    

    如果word没有关联值,那么将word与1关联;如果word已经关联值,那么将关联的值和1作为参数传递给sum

Other

子范围

集合中可以取出子范围:subList(start,end)

对这个子范围的对象进行操作的时候会影响原集合

var link = new LinkedList<String>();
for(int i=1;i<=10;i++){
    link.addLast(String.valueOf(i*9/4*3/2*5+104));
}

System.out.println(link.size());
var sub=link.subList(0,5);		//取出子范围
sub.clear();			//子范围的操作会影响原集合
System.out.println(link.size());

sub的添加、删除都会影响到link集合

同步

如果需要确保集合的线程安全,那么需要用到Collections类的synchronized***

例如需要一个线程安全的映射

var hash=new HashMap<String,Integer>();
...
var safe=Collections.synchronizedMap(hash);		//获取这个映射的安全映射
safe.add("NIHAO");		//会影响原映射
...

这和上面的子范围一样,safe的添加、删除都会影响到原映射hash

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值