Java复习之集合框架(二)

具体集合

LinkedList

链表是一个有序集合,每个对象的位置十分重要。add方法添加到链表的尾部,但是经常需要将元素添加到链表中间,由于迭代器描述了集合中的位置,所以这种依赖于位置的add方法由迭代器负责。实际上由于LinkedList和ArrayList都implements List所以LinkedList也实现了set/get方法,但是这两种方法的内部实现实际上是链表的遍历,效率很低(有一个优化就是如果要查找的元素时大于size/2的话从后往前查找),注意是属于集合自己的get/set方法而不是迭代器的
集合类库提供了两种迭代器

interface ListIterator<E> implements Iterator{
   void add(E element);
   //只有对自然有序的集合使用迭代器添加元素才有意义
//这个方法不返回boolean类型的值,因为默认每次都对链表进行修改
   E previous();
   boolean hasprevious();
   //返回previous越过的对象
   E remove();
   //不能连续调用两次remove
   //add方法只依赖迭代器的位置,而remove还依赖于迭代器的状态
   void set(E newElement);
   //更改前一个数值,如果在上一个next或previous调用之后列表结构被修改了,将抛出一个IllegalStateException的异常
}
//linkedlist类的listIterator方法返回了一个实现了listIterator接口的迭代器对象
Iterator

⚠️有一个问题
如果一个集合同时被多个迭代器所更改,则会出现错误,抛出ConcurrentModificationException异常。

List<String> list=..
ListIterator<String> it1 = list.ListIterator();
ListIterator<String> it2 = list.ListIterator();
it1.next();
it1.remove();
it2.next();

it2在访问的时候会出现上述异常,it2检测出这个链表从外部被修改了
一般有多个迭代器只读,或者只有一个迭代器可以同时读写

原理

每个迭代器都会维护一个更改操作数,每次对集合进行操作的时候会检测自己的更改操作数和集合的更改操作数是否相同,如果不同抛出异常。注意set方法不视为是对集合的结构性修改

public class Main {

    public static void main(String[] args) {
        var a = new LinkedList<String>();
        a.add("Amy");
        a.add("Carl");
        a.add("Erica");
        var b = new LinkedList<String>();
        b.add("bob");
        b.add("doug");
        b.add("frances");
        b.add("gloria");
        ListIterator<String> aIter = a.listIterator();
        Iterator<String> bIter = b.iterator();
        while(bIter.hasNext())
        {
            if(aIter.hasNext())
                aIter.next();
            aIter.add(bIter.next());
        }//每个都插入到其当前的iterator所指位置的前方
        System.out.println(a);
        //还有一个问题要注意:使用listIterator才可以在list前面进行元素的添加
        bIter = b.iterator();
        System.out.println(b);
        while(bIter.hasNext())
        {
            bIter.next();
            if(bIter.hasNext())
            {
                bIter.next();
                bIter.remove();//remove之前必须有next,不能remove两次,每次都是iterator之前的元素被删除
            }
        }
        System.out.println(b);
        a.removeAll(b);
        System.out.println(a);
    }
}

结果:
[Amy, bob, Carl, doug, Erica, frances, gloria]
[bob, doug, frances, gloria]
[bob, frances]
[Amy, Carl, doug, Erica, gloria]


数组列表ArrayList

在需要动态数组的时候可能使用Vector类进行操作,因为Vector是线程安全的,时刻保持同步改变,而ArrayList不是线程安全的。但是注意在不需要同步的时候尽量使用ArrayList,否则会在维护同步操作上浪费时间。

散列表

应用场景:想要找到元素,但是只知道这个元素的具体数据却不知道位置,如果用LinkedListArrayList需要进行元素的全部遍历,如果不在意元素的顺序,能够快速的查找出元素,但是无法控制元素的顺序也成了它的缺点


    public static void main(String[] args) {
      var words = new HashSet<String>();
      long totalTime = 0;
      try (var in = new Scanner(System.in)){
          while (in.hasNext())
          {

              String word = in.next();
              if(word=="yes")break;
              long callTime = System.currentTimeMillis();
              words.add(word);
              callTime = System.currentTimeMillis()-callTime;
              totalTime+=callTime;
          }
      }
      Iterator<String> iter = words.iterator();
      for(int i=1;i<=20&&iter.hasNext();i++)
      {
          System.out.println(iter.next());
      }

    }

其实iterator的hasnext和scanner类的有点相似

  • 散列表为每个对象计算一个整数,即散列码。每个对象根据自己的实例字段进行赋值,要保证hashcode和equals函数是统一的。在java中,散列表用链表数组实现,每个列表称为桶。在查找位置的时候首先用hashcode除以桶的总数取余确定是哪个桶,然后在桶中继续查找。每个桶中的散列值相同,标准类库使用的桶数是2的幂次,默认值为16。
  • 再散列,如果要插入到链表的数据过多,就得再散列。
  • 散列因子,可以确定何时对散列表进行再散列。默认值0.75,大于自动再散列。

树集

树集的内部是用红黑树实现的,是一个有序集合,可以以任意顺序插入到集合中。在对集合进行遍历时,值将自动按照排序后的顺序呈现。要实现树集必须实现Comparable接口或者构造集的时候必须提供一个Comparator

队列和双端队列

队列允许在队头高效删除元素,在队尾高效删除元素。双端队列允许两端
Deque接口在LinkedLIst和ArrayDeque都实现了

Queue

boolean add(E element);//正常返回true,如果队列已满,抛出IllegalStateException的异常
boolean offer(E element);//正常返回true,如果队列已满,返回false
 
E remove();//删除并返回队头元素,如果队列为空抛出NoSuchElementException的异常
E poll();//删除并返回队头元素,如果队列为空返回false

E element();//如果队列为空,返回队头元素,否则NoSuchElementException异常
E peek();//返回false

Deque

void addFirst(E element);
void addLast(E element);
boolean offerFirst(E element);
boolean offerLast(E element);

E removeFirst();
E removeLast();
E pollFirst();
E pollLast();

E getFirst();
E getLast():
E peekFirst();
E peekLast();

优先队列

优先队列中的元素可以按照任意顺序插入,但是会按照有序的顺序进行检索。无论何时调用remove方法,总会获得当前优先队列中最小元素

  • 使用了,必须也要实现Comparable接口或者Comparator构造器。优先队列并没有对所有元素进行排序,而是利用堆的特点
PriorityQueue();
PriorityQueue(int initialCapacity):
PriorityQueue(int initialCapacity,Comparator<? super E> c);
//指定构造器



 public static void main(String[] args) {
     var pq = new PriorityQueue<LocalDate>();
        pq.add(LocalDate.of(1906,12,9));
        pq.add( LocalDate.of(1815,12,9));
        pq.add( LocalDate.of(1903,12,9));
        pq.add( LocalDate.of(1910,12,9));

        System.out.println("Iterationg  over elements");
        for(LocalDate date : pq)
            System.out.println(date);
        System.out.println("ermoveing  elements");
        while (!pq.isEmpty())
            System.out.println(pq.remove());
    }

结果:
Iterationg  over elements
1815-12-09
1906-12-09
1903-12-09
1910-12-09
ermoveing  elements
1815-12-09
1903-12-09
1906-12-09
1910-12-09

进行迭代处理(for循环)的时候不是按照有序顺序来访问元素的
删除操作是按照从小到大删除的

映射

  1. 基本映射操作
    HashMap:散列映射对键进行散列
    TreeMap:树映射根据键的顺序将元素组织为一个搜索树
    有时对应的键没有出现在映射中,可以使用一个好的默认值
Map<String,Integer> scores=..
int score = scores.getOrderDefault(id,0);
//这样会在没有id对应的值存在的时候返回0
//即可以根据需要自行选择返回值
Map相关函数
V get(Object key);
default V getOrDefault(Object key,V defaultValue);
//寻找关联值,如果没有返回defaultValue
V put(K key,V value);
//如果这个key对应的值已经存在,进行替换并返回原来的值
//如果之前没有这个键,返回null
boolean containsKey(Object key);
boolean containsValue(Object value);

void putAll(Map<? extends K,? extends V> entries);
//lambda表达式
default void forEach(BiConsumer<? super K,? super V> action);

更新映射条目
比如用映射统计一个单词在文章中出现的次数:就是每次当遇到下一个单词的时候将对应的map中的该单词对应的value值+1即可

counts.put(word,counts.get(word)+1);
//但是有一个问题,就是第一个元素get会返回null

//第一种解决方案
counts.put(word,counts.getOrDefault(word,0)+1);
//第二种
counts.putIfAbsent(word,0);
counts.put(word,counts.get(word)+1);
//第三种
counts.merge(word,1,Integer::sum);
如果原先的值不存在,将使用这个调用把word和1相关联,否则使用Integer::sum组合原值和1
映射视图

集合框架不认为映射本身是一个集合。不过,可以得到映射的视图——实现了Collection接口或某个子接口的对象。

Set<K> keySet();
Collection<V> values():
Set<Map.Entry<K,V>>entrySet();

弱散列映射
  • WeakHashMap是未来解决一个问题:如果有一个值它对应的键在程序中已经没有任何一个位置会引用,但并不会被GC回收,因为只要映射对象是活动的,其中的所有桶也是活动的,不能被回收。
    WeakHashMap使用弱引用(Weak references)保存键。WeakReference对象将包含另一个对象的引用,这个对象就是散列表键
  • 如果垃圾回收器发现某个对象已经没有他人引用了,就将其回收。然而,如果某个对象只能由WeakReference引用,垃圾回收器也会将其回收,但引用这个对象的弱引用放入一个队列
WeakHashMap();
WeakHashMap(int initialCapacity);
WeakHashMap(int initialCapacity,float loadFactor);
//给定容量和填充因子
链接散列集与映射

LinkedHashMap和LinkedHashSet

  • 记住插入元素项的顺序。这样可以避免散列表中的项看起来是随机的
  • 链接散列映射可以使用访问顺序而不是插入顺序来迭代处理映射条目
LinkedHashMap();
LinkedHashMap(int initialCapacity):
LinkedHashMap(int initialCapacity,float loadFactor);
LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder);
//accessOrder表示构造顺序,true表示访问顺序,false表示插入顺序
protected boolean removeEldestEntry(Map.entry<K,V> eldest);
//如果要删除eldest元素,就要覆盖为返回true。eldest参数是预期可能删除的元素,这个方法在想映射中太假一个元素之后调用,其默认实现返回false。在默认情况下,老元素不会被删除。但是可以重新选择定义这个方法,以便有选择的返回true。比如,如果最老的元素符合一个条件或者映射潮服哦了一定的大小则返回true

枚举集与映射

EnumSet内部用位序列实现。如果对应的值在集中,则相应位置被置为1.

static <E extends Enum<E>> EnumSet<E> allOf(Class<E> enumType);
//返回包含给定枚举类型的所有值的可变集
static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> enumType);
//返回一个初始集合为空的集合
static <E extends Enum<E>> EnumSet<E> range(E from,E to);
//返回给定范围,包括边界,的集合
static <E extends Enum<E>> EnumSet<E> of(E e);
//后面的参数里面可以任意变化,返回一个可变集合

EnumSet没有公共的构造器,要使用静态工厂方法构造这个集

标识散列映射
  • 散列值是使用System.identityHashCode()计算的,根据内存地址计算
  • 进行比较时使用==,而不是equals
  • 不同键对象即使内容相同,也视为不同对象,在实现对象遍历算法时,这个类非常有用,可以用来跟踪哪些对象已经遍历过
static int identityHashCode(Object obj);
视图与包装器

视图
比如keySet看起来好像是返回了一个新的集合,然后把映射的所有键填入,但是实际上返回的是一个实现了Set接口的对象,由这个类的方法直接操控原映射

小集合

静态方法,可以生成给定元素的集合或列表,以及给定键/值对的映射

List<String> names = List.of("Peter","Paul","Mary");
Set<Integer> numbers = Set.of(2,3,4);
Map<String,Integer> scores.Map.of("Peter",2,"Paul",3);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值