Java基础知识:集合(上)

  • 利用Java类库帮助程序设计中实现传统的数据结构:如何使用标准库中的集合类。

 

  • Java集合框架:
  • 将集合的接口与实现分离:
    • 队列接口:指出可以在队列的尾部添加元素(入队),在队列的头部删除元素(出队),并且可以查找队列中元素的个数。队列的实现方式通常是:循环数组或使用链表
    • 如果需要一个循环数组队列,可以使用 ArrayDeque类。如果需要一个链表队列,就使用 LinkedList类(实现了Queue接口)。
    • 可以使用 接口类型 存放 集合的引用,这样,如果需要改变想法,就可以轻松使用另一种不同的实现
  • Collection 接口:
    • 在Java类库中,集合类的基本接口是 Collection接口
  • 迭代器:
public interface Iterator<E>
{
    E next();
    boolean hasNext();
    void remove();
    default void forEachRemaining( Consumer<? super E> action );
}
  • 如果到达了集合的末尾,next方法将抛出一个 NoSuchElementException。因此调用 next方法前 需要 调用 hasNext方法检查。
  • “for each”循环可以与任何实现了 Iterator接口 的对象一起工作。这个接口只包含一个抽象方法:Iterator<E> iterator() 。
  • Java SE 8 中,可以使用 : iterator.forEachRemaining( element -> do something with element ) 。
  • Java中,迭代器查找操作与位置变更是紧密相连的。查找一个元素的唯一方法是调用next,而执行查找操作的同时,迭代器的位置随之向前移动
  • Iterator接口的 remove方法 将会删除上次调用 next方法时返回的元素。
  • next方法和remove方法具有互相依赖性。如果调用remove之前没有调用next将是不合法的,会抛出IllegalStateException异常。(先调用next方法越过要删除的元素)

 

  • 泛型实用方法:
    • java.util.Collection<E>:
      • Iterator<E> iterator()
      • int size()
      • boolean isEmpty()
      • boolean contains( Object obj )
      • boolean containsAll( Collection<?> other )
      • boolean add( Object element )
      • boolean addAll( Collection<? extends E> other )
      • boolean remove( Object obj )
      • boolean removeAll( Collection<?> other )
      • default boolean removeIf( Predicate<? super E> filter )
      • void clear()
      • boolean retainAll( Collection<?> other ) //交集
      • Object[] toArray()
      • <T> T[] toArray( T[] arrayToFill )
  • 集合框架中的接口:
    • 集合有两个基本接口: Collection 和 Map 。
    • 标记接口 RandomAccess :测试一个特定的集合是否支持高效的随机访问。

 

  • 具体的集合:
    • ArrayList 一种可以动态增长和缩减的索引序列。
    • LinkedList 一种可以在任何位置进行高效地插入和删除操作的有序序列。
    • ArrayDeque 一种用循环数组实现的双端队列。
    • HashSet 一种没有重复元素的无序集合。
    • TreeSet 一种有序集。
    • EnumSet 一种包含枚举类型值的集。
    • LinkedHashSet 一种可以记住元素插入次序的集。
    • PriorityQueue  优先队列
    • HashMap 一种存储 键/值 关联的数据结构。
    • TreeMap 一种 键值 有序排列的映射表。
    • EnumMap 一种键值属于枚举类型的映射表。
    • LinkedHashMap 一种可以记住 键/值 添加次序的映射表。
    • WeakHashMap 一种无用之后,可以被垃圾回收器回收的映射表。
    • IdentityHashMap 一种用==而不是用equals比较键值的映射表。
  • 链表:
    • 数组和数组列表的重大缺陷:从数组中间删除或插入一个元素需要付出很大的代价。
    • 使用链表就可以解决以上的问题。
    • 在 Java 中,所有链表实际上都是双向链接的。(doubly linked)
    • 链表与泛型集合之间有一个重要的区别:链表是一个有序集合,每个对象的位置十分重要。而迭代器是描述集合中位置的,所以只有对自然有序的集合使用迭代器添加元素才有实际意义。故Iterator的 子接口 ListIterator 中包含 add方法,该add方法不会返回布尔值,总是假定添加操作会改变链表,add方法会在迭代器位置之前添加一个对象。(链表的add方法只会将对象添加到链表的尾部)
    • ListIterator接口有两个方法,可以用于反向遍历链表。 E previous() ;boolean hasPrevious() 。
    • LinkedList类的 ListIterator方法 返回一个实现了 ListIterator接口的 迭代器对象
    • add方法只依赖于迭代器的位置,而remove方法依赖于迭代器的状态(上一步是next还是previous)。
    • 如果迭代器发现它的集合被另一个迭代器修改了,或是该被集合自身的方法修改了,就会抛出一个ConcurrentModificationException异常。(并发修改异常)
    • 为了避免并发修改异常,遵循以下原则:可以根据需要给容器附加许多的迭代器,但只读。另外,再单独附加一个既可读又可写的迭代器。
    • java.util.List<E>:
      • ListIterator<E> listIterator()
      • ListIterator<E> listIterator( int index )
      • void add( int i, E element )
      • void addAll( int i, Collection<? extends E> elements )
      • E remove(int i)
      • E get(int i)
      • E set(int i, E element)
      • int indexOf( Object element )
      • int lastIndexOf( Object element ) 
    • java.util.ListIterator<E>
      • void add(E newElement)
      • void set(E newElement)
      •  boolean hasPrevious()
      • E previous()
      • int nextIndex()
      • int previousIndex()
    • java.util.LinkedList<E>
      • LinkedList()
      • LinkedList( Collection<? extends E> elements )
      • void addFirst( E element )
      • void addLast( E element )
      • E getFirst()
      • E getLast()
      • E removeFirst()
      • E removeLast()
    • 示例程序:(交错合并两个链表,对其中一个链表每隔一个元素进行一次删除,测试removeAll方法)
package linkedList;

import java.util.*;
public class LinkedListTest {
	public static void main( String[] args ) {
		List<String> list1 = new LinkedList<>();
		List<String> list2 = new LinkedList<>();
		
		list1.add("Antony");
		list2.add("Bryant");
		list1.add("Carmelo");
		list2.add("Davis");
		list1.add("Evans");
		list2.add("Franklin");
		list2.add("George");
		System.out.println("Original list1 and list2: ");
		System.out.println(list1);
		System.out.println(list2);
		
		ListIterator<String> iter1 = list1.listIterator();
		Iterator<String> iter2 = list2.iterator();
		
		while( iter2.hasNext() ) {
			if( iter1.hasNext() ) iter1.next();
			iter1.add(iter2.next());
		}
		System.out.println("list1 after merge: ");
		System.out.println(list1);
		
		iter2 = list2.iterator();
		while( iter2.hasNext() ) {
			iter2.next();
			if( iter2.hasNext() ) {
				iter2.next();
				iter2.remove();
			}
		}
		System.out.println("list1 and list2 after list2's removal: ");
		System.out.println(list1);
		System.out.println(list2);
		
		list1.removeAll(list2);
		System.out.println("list1 after removing all the elemnts in list2(current): ");
		System.out.println(list1);
	}
}

 

  • 数组列表:ArrayList类。这个类也实现了 List接口。ArrayList封装了一个动态再分配的对象数组。适用于随机访问修改元素。
  • Vector类与ArrayList类:Vector类的所有方法都是同步的,可以两个线程安全地访问一个Vector对象。但,如果由一个线程访问Vector,代码要在同步操作上耗费大量时间。故,建议在不需要同步的时候使用ArrayList,而不要使用Vector。

 

  • 散列集:
    • 在Java中,散列表用链表数组实现。每个列表被称为”桶“。
    • 在 Java SE 8 中,桶时会从链表变为平衡二叉树。
    • 如果想更多地控制散列表的运行性能,就要指定一个初始的桶数
    • 通常桶数设置为预计元素的75%到150%,最好将桶数设置为一个素数。(防止键的聚集)
    • 标准库使用的桶数是2的幂,默认值为16
    • 装填因子为0.75(默认值),表中超过75%的位置填入元素则这个表就会以双倍的桶数进行再散列。
    • java.util.HashSet<E>
      • HashSet()
      • HashSet( Collection<? extends E> elements )
      • HashSet(int capacity)
      • HashSet(int capacity, float loadFactor)
    • 示例程序:(测试HashSet,打开一个文本文件,输出里面的不重复单词数,以及添加所有单词到HashSet的总耗时多少毫秒)
package set;

import java.util.*;
import java.io.*;
public class SetTest {
	public static void main( String[] args ) {
		Set<String> words = new HashSet<>();
		long totalTime = 0;
		
		try( Scanner in = new Scanner(new File("C:\\Users\\86188\\alice30.txt") ) ){
			while( in.hasNext() ) {
				String word = in.next();
				long callTime = System.currentTimeMillis();
				words.add(word);
				callTime = System.currentTimeMillis() - callTime;
				totalTime += callTime;
			}
		}catch(Exception e ) {
			e.printStackTrace();
		}
		
		Iterator<String> iter = words.iterator();
		for( int i = 1; i <= 20 && iter.hasNext(); i++ ) {
			System.out.println(iter.next());
		}
		System.out.println("...");
		System.out.println(words.size() + " distinct words. " + totalTime + " milliseconds." );
	}
}

 

  • 树集:
    • TreeSet类与散列集十分类似。树集是一个有序集合(排序的实现是红黑树)。
    • 因此将一个元素添加到树中要比添加到散列表中要慢。但是检查数组或链表中的重复元素相比还是快很多。
    • 要使用树集,必须能够比较元素,要么元素实现了Comparable接口,要么提供一个比较器Comparator。
    • 从 Java SE 6 开始,TreeSet类实现了 NavigableSet接口。
    • java.util.TreeSet<E>:
      • TreeSet()
      • TreeSet( Comparator<? super E> comparator )
      • TreeSet( Collection<? extends E> elements )
      • TreeSet( SortedSet<E> s )
    • java.util.SortedSet<E>:
      • Comparator<? super E> comparator() //如果元素是使用Comparable接口的compareTo方法比较就返回null 。
      • E first() //返回最小元素
      • E last() //返回最大元素
    • java.util.NavigableSet<E>
      • E higher(E value)
      • E lower(E value) //返回大于value的最小元素或小于value的最大元素
      • E ceiling(E value) 
      • E floor(E value) //返回大于等于 value的最小元素或 小于等于 value 的最大元素。
      • E pollFirst()
      • E pollLast() //删除并返回最大元素或最小元素
      • Iterator<E> descendingIterator() //返回一个递减顺序遍历集中元素的迭代器。 
    • 示例程序:(测试树集,利用树集 按照compareTo方法 和 比较器的方式 对多个对象进行排序)
package treeSet;

import java.util.*;
public class TreeSetTest {
	public static void main( String[] args ) {
		SortedSet<Item> sortedByCompareTo = new TreeSet<>();
		sortedByCompareTo.add(new Item("Atlanta", 321) );
		sortedByCompareTo.add(new Item("Boston", 231 ) );
		sortedByCompareTo.add(new Item("Chicago",123 ) );
		
		System.out.println(sortedByCompareTo);
		System.out.println();
		NavigableSet<Item> sortedByName = new TreeSet<>(Comparator.comparing(Item::getName));
		sortedByName.addAll(sortedByCompareTo);
		
		System.out.println(sortedByName);
	}
}
package treeSet;

import java.util.Objects;

public class Item implements Comparable<Item>{
	private String name;
	private int id;
	
	public Item() { name = ""; id = -1; }
	
	public Item( String name, int id ) {
		this.name = name;
		this.id = id;
	}
	
	public String getName() { return name; }
	
	public int getId() { return id; }
	
	public String toString() {
		return "[name=" + name + ", id=" + id + "]"; 
	}
	
	public boolean equals( Object other ) {
		if( this == other ) return true;
		if( other == null ) return false;
		if( this.getClass() != other.getClass() ) return false;
		Item otherItem = (Item) other;
		return Objects.equals(name, otherItem.name) && id == otherItem.id;
	}
	
	public int hashCode() { return Objects.hash(name,id); }
	
	public int compareTo( Item other ) {
		int diff = Integer.compare(id, other.id);
		if( diff != 0 ) return diff;
		return name.compareTo(other.name);
	}
}

 

 

  • 队列与双端队列:
    • 在 Java SE 6中,引入了 Deque接口, 并由 ArrayDeque类  和 LinkedList类 实现。
    • java.util.Queue<E>
      • boolean add( E element ) //队尾入队,若队列满了,抛出  IllegalStateException 异常。
      • boolean offer( E element ) //队尾入队,若队列满了,返回false。
      • E remove() //删除并返回队头元素。若队头为空,抛出 NoSuchElementException 。
      • E poll() //功能同上,但若队头为空,返回null 。
      • E element()
      • E peek() //类似。
    • java.util.Deque<E>
      • void addFirst( E element )
      • void addLast( E element )
      • boolean offerFirst( E element )
      • boolean offerLast( E element )
      • E removeFirst/Last()
      • E pollFirst/Last()
      • E getFirst/Last()
      • E peekFirst/Last()
    • java.util.ArrayDeque<E>
      • ArrayDeque() //初始容量默认为16
      • ArrayDeque(int initialCapacity) //给定初始容量的无限双端队列
  • 优先级队列:
    • 无论何时调用remove方法,总会获得当前优先队列中最小的元素。
    • 与TreeSet一样,一个优先队列既可以保存实现了Comparable接口的对象,也可以保存在构造器中提供的 Comparator对象。
    • java.util.PriorityQueue<E>
      • PriorityQueue()
      • PriorityQueue( int initialCapacity )
      • PriorityQueue( int initialCapacity, Comparator<? super E> c )
    • 示例程序:(测试优先队列)
package priorityQueue;

import java.time.*;
import java.util.*;

public class PriorityQueueTest {
	public static void main( String[] args ) {
		PriorityQueue<LocalDate> heap = new PriorityQueue<>();
		heap.add(LocalDate.of(1906, 12, 9));
		heap.add(LocalDate.of(1815, 12, 10));
		heap.add(LocalDate.of(1903, 12, 3));
		heap.add(LocalDate.of(1910, 6, 22));
		//迭代顺序访问优先队列的时候,貌似是采用了层序遍历
		System.out.println("Iterating over elements...:");
		for( LocalDate d : heap )
			System.out.println(d);
		
		System.out.println();
		System.out.println("Removing elements...:");
		while( !heap.isEmpty() ) {
			System.out.println(heap.remove());
		}
	}
}

 

  • 映射:用来存放 键/值 对。可以快速查找现有的元素。
  • 基本映射操作:
    • Java类库为映射提供了两个通用的实现:HashMapTreeMap。这两个类都实现了 Map接口
    • 映射散列对进行散列。树映射用的整体顺序对元素进行排序,并将其组织成搜索树
    • 散列或比较函数能作用于 ,与键关联的值不能进行散列或比较。
    • 键必须唯一,如果对同一键两次调用put方法,会覆盖(但返回的是上一个值)。
    • 要迭代处理映射的键和值,最容易的方法是使用 forEach方法,可以提供一个接收键和值的 lambda表达式。如:Scores.forEach( (k,v) -> System.out.println( "key: " + k + ", value: " + v ) ) 。
    • java.util.Map<K,V>
      • V get( Object key ) //键可以为null
      • default V getOrDefault( Object key, V defaultValue )
      • V put( K key, V value )  // 返回键的旧值,如果键没有出现过就返回null。
      • void putAll( Map<? extends K, ? extends V> entries )
      • boolean containsKey( Object key )
      • boolean containsValue( Object value )
      • default void forEach( BiConsumer<? super K, ? super V> action )
    • java.util.HashMap<K,V>
      • HashMap()
      • HashMap(int initialCapacity)
      • HashMap( int initialCapacity, float loadFactor )
    • java.util.TreeMap<K,V>
      • TreeMap()
      • TreeMap( Comarator<? super K> c )
      • TreeMap( Map<? extends K, ? extends V> entries )
      • TreeMap( SortedMap<? extends K, ? extends V> entries )
    • java.util.SortedMap<K,V>
      • Comparator<? super K> comparator()
      • K firstKey()
      • K lastKey()
    • 示例程序:(测试映射HashMap)
package map;

import java.util.*;
public class MapTest {
	public static void main( String[] args ) {
		Map<String,String> info = new HashMap<>();
		info.put("144-25-5464","Amy Lee");
		info.put("567-24-2546", "Harry Hacker");
		info.put("157-62-7935", "Gary Cooper");
		info.put("456-62-5527","Francesca Cruz");
		
		System.out.println(info);
		//remove
		info.remove("567-24-2546");
		//replace
		info.put("456-62-5527", "Francesca Miller");
		
		//look up
		System.out.println(info.get("157-62-7935"));
		
		//iterate through all entries
		info.forEach((k,v) -> System.out.println("Key: " + k + ", value: " + v ) );
		
	}
}

 

  • 更新映射项:
    • 处理映射时的一个难点就是更新映射项。特殊情况:键第一次出现的更新。
    • 比如,处理一个单词出现的频率:
      • counts.put( word, counts.getOrDefault(word,0) + 1 )
      • 1.counts.putIfAbsent(word,0)  2.counts.put(word, counts.get(word) + 1 )
      • merge方法。 counts.merge( word, 1, Integer::sum )
    • java.util.Map<K,V>(以下为一些更新键值的方法,虽不甚常用)
      • default V merge( K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction ) //如果key与一个非null值v关联,将函数应用到v和value,将key与结果关联,或者如果结果为null,则删除这个键。否则,将key与value关联,返回get(key)。
      • default V compute( K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction ) ,将函数应用到key和 get(key),将key与结果关联,如果结果为null,则删除此键。
      • computeIfPresent方法
      • computeIfAbsent方法
      • replaceAll方法
    • 映射视图:
      • 集合框架不认为映射本身是一个集合。不过,可以得到映射的视图(view)——实现了Collection接口或某个子接口
      • 有三种视图:键集值集合(不是一个集,因为可重复) 以及 键/值 对集
      • Set<K> KeySet() //不是HashSet或TreeSet实现的。
      • Collection<V> values()
      • Set<Map.Entry<K,V>> entrySet()  //如果想同时查看键和值,可以通过枚举条目(entries)来避免查找值。
      • 如果在键值视图上调用迭代器的remove方法,实际上会从映射中删除这个 键和与它相关联的值。
      • 通过视图删但不可增
      • java.util.Map.Entry<K,V>
        • K getKey()
        • V getValue()
        • V setValue(V newValue)
    • 弱散列映射:
      • 垃圾回收器跟踪活动的对象。只要映射的对象是活动的,其中所有的桶都是活动的,它们就不能被回收
      • 使用WeakHashMap时,当对唯一引用来自散列条目时,这一数据结构将与垃圾回收器协同工作一起删除 键/值对。
    • 链接散列集 与 映射:
      • LinkedHashSet 和 LinkedHashMap 类 可以记住插入元素项的顺序
      • 实现方法是:在原本HashSet 和 HashMap 的基础上,当条目插入到表中时,就并入一个双向链表之中。尔后就可以利用这个双向链表进行迭代。
      • 可以构造一个用访问顺序的链接散列表,对条目进行迭代:
        • 每次调用get或put,收到影响的条目都从当前的位置删除,并放到条目链表的尾部
        • LinkedHashMap<K,V>(initialCapacity, loadFactor, true) //true为访问顺序,false为插入顺序。
        • 访问顺序对于实现 高速缓存的“最近最少使用”原则十分重要。
        • 构造一个LinkedHashMap的子类,并覆盖 protected boolean removeEldestEntry( Map<K,V> eldest ) 方法,每当方法返回true时,就添加一个新条目,从而导致删除eldest条目。这个方法在条目添加到映射中之后调用。
    • 枚举集与映射:
      • EnumSet是一个枚举类型元素集高效实现。
      • 由于枚举类型只有有限个示例,所以EnumSet内部用位序列实现。如果对应的值在集中,则对应的位被置为1。
      • EnumSet没有公共的构造器,可以使用静态工厂方法来构造这个集。
      • 可以使用 Set接口 的常用方法来修改EnumSet。
      • EnumMap是一个键类型为枚举类型的映射,它可以直接且高效地用一个 值数组 实现。如:EnumMap<Weekday,Employee> personInCharge = new EnumMap<>(Weekday.class) 。
      • java.util.EnumSet<E extends Enum<E>>
        • static <E extends Enum<E>> EnumSet<E> allOf( Class<E> enumType )
        • noneOf
        • range
        • of
      • java.util.EnumMap<K extends Enum<K>, V>
        • EnumMap( Class<K> keyType )
    • 标识散列映射:
      • 在这个类中,键的散列值不是用 hashCode函数 计算而是用 System.identityHashCode方法 计算的。散列值只与对象的内存地址有关。
      • 故 用 == 比较映射值而不用 equals 。
      • IdentityHashMap 。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值