迭代器
基本概念
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