Java:常用集合类(List、Map、Set、Queue、Stack)

时间:2022-08-18 17:39:01

迭代器

基本概念

Iterator接口包含3个方法:

public interface Iterator<E> {
E next();
boolean hasNext();
void remove();
}

Java集合类库中的迭代器与C++的STL中的迭代器有所区别。C++中的迭代器是根据数组索引建模的,可以直接根据迭代器查看元素,或者移动迭代器。但Java的迭代器却不是这样,它的查找操作与位置变更是紧密相关的,查找一个元素的唯一方法是调用next,与其同时迭代器向前移动。因此,应该将Java迭代器看作是位于两个元素之间,当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。

Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while (iter.hasNext()) {
String element = iter.next();
...
}

也可以用for each循环实现:

for(String element : c) {
...
}

编译器简单地将“for each”循环翻译成带有迭代器的循环。

删除元素

Iterator接口的remove方法将会删除上次调用next方法时返回的元素,如果想要删除指定位置上的元素,就需要先越过它。比如删除字符串集合中第一个元素:

Iterator<String> iter = c.iterator();
it.next();
it.remove();

注意,next方法和remove方法是相互依赖的,如果调用remove之前没有调用next,将是不合法的。例如,如果想删除两个相邻的元素,必须:

it.remove();
it.next();
it.remove();

具体集合类型

Java中的集合只能存放封装类,而不能存放基本数据类型。因此,如果要用到int或double,要用它们相对应的类:Integer和Double。

数组列表 ArrayList

ArrayList就是动态数组,类似C++中的vector容器。Java中,Vector类的所有方法都是同步的,可以由两个线程安全地访问一个Vector对象,但是,如果由一个线程访问Vector,会花费很长的时间来同步。ArrayList不是同步的,因此比较快速。

构造器

ArrayList提供了三个构造器:
public ArrayList();
默认的构造器,将会以默认(10)的大小来初始化内部的数组
public ArrayList(ICollection);
用一个ICollection对象来构造,并将该集合的元素添加到ArrayList
public ArrayList(int);
用指定的大小来初始化内部的数组

ArrayList<Employee> l = new ArrayList<>(); //构造一个ArrayList

实例化二维List时的注意点

如果是实例化一维List,可以这么写:

List<Integer> list = new ArrayList<Integer>()

然而,如果是实例化一个二维的List,就应该写成:

List<List<Integer>> list = new ArrayList<List<Integer>>();
//或将右侧尖括号里的东西省略:
List<List<Integer>> list = new ArrayList<>();

PS:
当为二维List添加一维List元素时,有一个需要注意的地方:

List<List<Integer>> result = new ArrayList<List<Integer>>();  
ArrayList<Integer> list = new ArrayList<Integer>();
...
result.add(list);//错误,list只是引用

result.add(new ArrayList<Integer>(list));//正确,必须重新实例化一个一维List

ArrayList转换成Arrays

List<String> list=new ArrayList<String>();  
list.add("王利虎");
list.add("张三");
list.add("李四");
int size=list.size();
String[] array = (String[])list.toArray(new String[size]);

ArrayList提供public <T> T[] toArray(T[] a)方法返回一个按照正确的顺序包含此列表中所有元素的数组。返回数组的运行时类型就是指定数组的运行时类型。如果列表能放入指定的数组,则返回放入此列表元素的数组。否则,将根据指定数组的运行时类型和此列表的大小分配一个新的数组。
如果指定的数组能容纳列表并有剩余空间(即数组的元素比列表的多),那么会将数组中紧跟在集合末尾的元素设置为 null。这对确定列表的长度很有用,但只在调用方知道列表中不包含任何 null 元素时才有用。

Arrays转换成ArrayList

当需要将数组转换成数组列表时,需要用到Arrays.asList方法:

String[] array=new String[3];  
array[0]="王利虎";
array[1]="张三";
array[2]="李四";
List<String> list=Arrays.asList(array);

asList方法返回一个受指定数组支持的固定大小的列表,此方法同 Collection.toArray 一起,充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。返回的列表是可序列化的,并且实现了 RandomAccess。除此之外, 此方法还提供了一个创建固定长度的列表的便捷方法,该列表被初始化为包含多个元素:

List<String> list = Arrays.asList("王利虎","张三","李四");  

注意:
如果数组是int数组,要将它转换成Integer的数组列表就比较麻烦,不能用上面的方法,还是老老实实用for循环。
将Integer数组列表转成int数组:

        List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
int[] array = new int[list.size()];
for(int i=0;i<list.size();i++) {
array[i]=list.get(i);
}
//System.out.println(Arrays.toString(array));

将int数组转成Integer数组列表:

        int[] array={1,2,3,4,5};
List<Integer> list = new ArrayList<>();
for(int i=0;i<array.length;i++) {
list.add(array[i]);
}

其他常用方法

l.size(); //返回l的元素个数

l.isEmpty(); //判断l是否为空

l.indexOf(e); //返回l中首次出现的指定元素e的索引;如果e不存在,则返回 -1

l.add(E element) //在当前位置添加一个元素
l.add(int i, E element) //在给定位置添加一个元素
l.addAll(i,l2); //将l2中的全部数据插入到到l的位置i上

l.set(i,x); //替换l[i]的元素值,前提是该位置原先就有值,否则会抛出一个异常

e = l.get(i); //获取指定位置的元素

l.remove(i); //删除元素
l.removeAll(l2); //按照l2中的数据来删除l

l.subList(int fromIndex, int toIndex)
//fromIndex:用于指定新列表的起始点(包括该点)。
//toIndex:用于指定新列表的结束点(不包括该点)。
//注意:这里如果要对获得的子list操作,原list也会发生改变。如果希望子list与原list相独立,应给写成:
List<Object> tempList = new ArrayList<Object>(lists.subList(2, lists.size()));

l.clear(); //清空l

l.ensureCapacity(100); //分配一个包含100个对象的确定大小的ArrayList

void Collections.reverse(l);//将l原地翻转

void Collections.sort(l);//将l原地排序

链表 LinkedList

数组和动态的ArrayList类有一个重大的缺陷,那就是从中间位置添加或删除一个元素要付出很大的代价。链表解决了这个问题。
常用的list操作有:

LinkedList() //构造一个空链表

ListIterator<E> listIterator() //返回一个列表迭代器

void add(E element) //在当前位置添加一个元素
void add(int i, E element) //在给定位置添加一个元素
void addFirst(E element)
void addLast(E element) //将某个元素添加到列表的头部或尾部
void addAll(int i,Collection<? extends E> elements) //将某个集合中的所有元素添加到给定位置

E remove(int i) //删除给定位置的元素并返回之
E removeFirst()
E removeLast()
//删除并返回列表头部或尾部的元素

E get(int i) //获取给定位置的元素
E getFirst()
E getLast()//返回列表头部或尾部的元素

void set(E element) //用新元素取代next或previous刚才访问的元素
E set(int i,E element) //用新元素取代给定位置的元素,并返回原来的元素

int indexOf(Object element) //返回与指定元素相等的元素在列表中第一次出现的位置,如果找不到将返回-1
int nextIndex() //返回下一次调用next方法时将返回的元素索引
int previousIndex() //返回下一次调用previous方法时将返回的元素索引

映射表 map

map用来存放键值对,并根据键查找值。Java类库提供了两种通用的实现:HashMap和TreeMap。
HashMap对键进行散列,是无序的,而TreeMap用键的整体顺序对元素进行排序。

void clear( )
//从此映射中移除所有映射关系(可选操作)。
boolean containsKey(Object k)
//如果此映射包含指定键的映射关系,则返回 true。
boolean containsValue(Object v)
//如果此映射将一个或多个键映射到指定值,则返回 true。
Set entrySet( )
//返回此映射中包含的映射关系的 Set 视图。
boolean equals(Object obj)
//比较指定的对象与此映射是否相等。
Object get(Object k)
//返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
int hashCode( )
//返回此映射的哈希码值。
boolean isEmpty( )
//如果此映射未包含键-值映射关系,则返回 true。
Set keySet( )
//返回此映射中包含的键的 Set 视图。
Object put(Object k, Object v)
//将指定的值与此映射中的指定键关联(可选操作)。
void putAll(Map m)
//从指定映射中将所有映射关系复制到此映射中(可选操作)。
Object remove(Object k)
//如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
int size( )
//返回此映射中的键-值映射关系数。
Collection values( )
//返回此映射中包含的值的 Collection 视图。

下面的例子来解释Map的功能:

      Map<String,String> m1 = new HashMap(); 
m1.put("Zara", "8");
m1.put("Mahnaz", "31");
m1.put("Ayan", "12");
m1.put("Daisy", "14");
System.out.println();
System.out.println(" Map Elements");
System.out.print("\t" + m1);

以上实例编译运行结果如下:

Map Elements
{Mahnaz=31, Ayan=12, Daisy=14, Zara=8}

Map.Entry类

Map的entrySet()方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键/值对。
接着,Map.Entry类提供了一个getKey()方法和一个getValue()方法,用来很方便地获取键和值。

Set entries = map.entrySet( );
if(entries != null) {
Iterator iterator = entries.iterator( );
while(iterator.hasNext( )) {
Map.Entry entry =iterator.next( );
Object key = entry.getKey( );
Object value = entry.getValue();
}
}

Map.Entry同时也提供了一个setValue()方法,程序员可以使用它修改map里面的值。

遍历Map中键值对的方法

方法一:在for-each循环中使用entrySet来遍历。

这是最常见的并且在大多数情况下也是最可取的遍历方式,在键值都需要时使用。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();
...
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

方法二:在for-each循环中遍历keys或values。
如果只需要map中的键或者值,你可以通过keySet或values来实现遍历,而不是用entrySet。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();  

//遍历map中的键

for (Integer key : map.keySet()) {

System.out.println("Key = " + key);

}

//遍历map中的值

for (Integer value : map.values()) {

System.out.println("Value = " + value);

}

集合 Set

散列集合 HashSet

       HashSet h=new HashSet();//声明

h.add("1st");//添加元素
h.add("2nd");

num = h.size();//HashSet的元素个数
Iterator it=h.iterator();//迭代器
while(it.hasNext()) {
Object o=it.next();
System.out.println(o);
}

h.remove("2nd");//删除元素
boolean contains = set.contains("apple");//查找元素

树集 TreeSet

HashSet是无序的,而TreeSet是基于红黑树的,内部是有序的。其常用操作如下:

int size() //返回 set 中的元素数(set 的容量)。

boolean add(E e) //将指定的元素添加到此 set(如果该元素尚未存在于 set 中)。

boolean addAll(Collection<? extendsE> c) //将指定 collection 中的所有元素添加到此 set 中。

Object clone() //返回TreeSet实例的浅表副本。

Comparator<? superE> comparator() //返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回null。

boolean contains(Object o) //如果此 set 包含指定的元素,则返回true。

Iterator<E> iterator() //返回在此 set 中的元素上按升序进行迭代的迭代器。

Iterator<E> descendingIterator() //返回在此 set 元素上按降序进行迭代的迭代器。

E first() //返回此 set 中当前第一个(最低)元素。

E last() //返回此 set 中当前最后一个(最高)元素。

E ceiling(E e) //返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回null。

E floor(E e) //返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回null。

E lower(E e) //返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回null。

E higher(E e) //返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回null。

boolean isEmpty() //如果此 set 不包含任何元素,则返回true。

E pollFirst() //获取并移除第一个(最低)元素;如果此 set 为空,则返回null。

E pollLast() //获取并移除最后一个(最高)元素;如果此 set 为空,则返回null。

boolean remove(Object o) //将指定的元素从 set 中移除(如果该元素存在于此 set 中)。

void clear() //移除此 set 中的所有元素。

队列与优先队列

队列 Queue

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

Queue的常用方法如下:

boolean isEmpty()
//如果队列为空,则返回true,否则返回false

boolean add(E element)
boolean offer(E element)
//如果队列没有满,将给定的元素添加到这个双端队列的尾部并返回true。否则,第一个方法抛出一个异常,第二个方法返回false

E remove()
E poll()
//删除并返回队首元素,如果队列是空的,第一个方法抛出一个异常,第二个方法返回null

E element()
E peek()
//返回队尾的元素,如果队列是空的,第一个方法抛出一个异常,第二个方法返回null

以下实例演示了队列(Queue)的用法:

import java.util.LinkedList;
import java.util.Queue;

public class Main {
public static void main(String[] args) {
//add()和remove()方法在失败的时候会抛出异常(不推荐)
Queue<String> queue = new LinkedList<String>();
//添加元素
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
queue.offer("e");
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("poll="+queue.poll()); //返回第一个元素,并在队列中删除
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("element="+queue.element()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("peek="+queue.peek()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
}
}

以上代码运行输出结果为:

a
b
c
d
e
===

poll=a
b
c
d
e
===

element=b
b
c
d
e
===

peek=b
b
c
d
e

优先队列 PriorityQueue

PriorityQueue是从JDK1.5开始提供的新的数据结构接口。
如果不提供Comparator的话,PriorityQueue中元素默认按自然顺序排列,也就是数字的话默认小的在队首,字符串的话按字典序排列。比如队列 1 3 5 10 2 自动会被排列成 1 2 3 5 10
如果想实现按照自己的意愿进行优先级排列的队列的话,需要实现Comparator接口。PriorityQueue的常用方法如下:

boolean isEmpty() //判断优先级队列是否为空。
int size() //此方法返回这个集合中元素的个数。
void clear() //此方法删除所有来自此优先级队列中的元素。

Comparator<? super E> comparator() //此方法返回用于排序的比较器。
Iterator<E> iterator() //此方法返回一个迭代器。

boolean add(E e) //此方法将指定元素插入此优先级队列。
boolean off(E e) //同样是插入元素。
boolean remove(Object o) //这个方法从队列中移除指定元素的单个实例(如果存在)。
boolean contains(Object o) //如果此队列包含指定的元素,此方法返回true。
E peek() //此方法检索此队列的头,如果此队列为空,则返回null。
E poll() //此方法检索并移除此队列的头,如果此队列为空,则返回null。

Object[] toArray() //这个方法返回一个包含此队列所有元素的数组。

下面演示具体用法:

package com.javaer.examples.datastruct;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import org.apache.poi.ss.formula.functions.Count;

public class PriorityQueueExample {
public static void main(String[] args) {
// 定义一个PriorityQueue
Queue<Integer> qi = new PriorityQueue<Integer>();
qi.add(5);
qi.add(2);
qi.add(1);
qi.add(10);
qi.add(3);

while (!qi.isEmpty()) System.out.print(qi.poll() + ",");
System.out.println();
System.out.println("-----------------------------");
//实现一个比较器
Comparator<Integer> cmp;
cmp = new Comparator<Integer>() {
public int compare(Integer e1, Integer e2) {
return e2 - e1;
}
};
Queue<Integer> q2 = new PriorityQueue<Integer>(5,cmp);
q2.add(2);
q2.add(8);
q2.add(9);
q2.add(1);
while (!q2.isEmpty()) System.out.print(q2.poll() + ",");
}
}

输出结果是:

1,2,3,5,10,
-----------------------------

9,8,2,1,

此队列的头是按指定排序方式的最小元素。如果多个元素都是最小值,则头是其中随机的一个元素。

栈 Stack

栈是Vector的一个子类,它实现了一个标准的后进先出的数据结构。
堆栈只定义了默认构造函数,用来创建一个空栈。 堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法。


值得一提的是,Java中的栈Stack的元素类型不限,同一个Stack中可以存入Integer、Double等不同的数据类型。

boolean empty() //测试堆栈是否为空。

Object peek( ) //查看堆栈顶部的对象,但不从堆栈中移除它。

Object pop( ) //移除堆栈顶部的对象,并作为此函数的值返回该对象。

Object push(Object element) //把项压入堆栈顶部。

int search(Object element) //返回对象在堆栈中的位置,以 1 为基数。

下面的程序说明Stack所支持的几种方法:

import java.util.*;

public class StackDemo {
//显示压栈的结果
static void showpush(Stack st, int a) {
st.push(new Integer(a));
System.out.println("push(" + a + ")");
System.out.println("stack: " + st);
}
//显示弹栈的结果
static void showpop(Stack st) {
System.out.print("pop -> ");
Integer a = (Integer) st.pop();
System.out.println(a);
System.out.println("stack: " + st);
}
//主函数
public static void main(String args[]) {
Stack st = new Stack();
System.out.println("stack: " + st);
showpush(st, 42);
showpush(st, 66);
showpush(st, 99);
showpop(st);
showpop(st);
showpop(st);
//捕获到弹栈异常,此时Stack为空
try {
showpop(st);
} catch (EmptyStackException e) {
System.out.println("empty stack");
}
}
}

以上实例编译运行结果如下:

stack: [ ]
push(42)
stack: [42]
push(66)
stack: [42, 66]
push(99)
stack: [42, 66, 99]
pop -> 99
stack: [42, 66]
pop -> 66
stack: [42]
pop -> 42
stack: [ ]
pop -> empty stack