Java容器入门
Java语言提供了大量的集合框架,用户可以直接使用,这里对常用的集合接口以及实现做一简单总结。
整体框架
接口介绍
Iterable接口
迭代器接口,提供用于遍历、选择容器元素的迭代器,由方法iterator()返回一个Iterator类型的对象,该对象提供了遍历的各种方法:
1.hasNext()
判断是否还存在下一个元素,如果存在返回true。
2.next()
获得下一个元素对象。
3.remove()
从集合中删除一个元素,对于需要在循环过程删除元素的,一定要用迭代器进行删除
Collection接口
Collection是一个集合的抽象,提供了集合的各种方法:
查询操作
1.size(),isEmpty()
用于获取集合的元素个数,以及判断集合是否为空。不同实现类中size的实现细节不同,尤其是并发的集合实现,size可能需要遍历整个集合,使用的时候一定要注意对性能的影响。
用于判断集合是否为空,最好使用isEmpty(),尽量不要用size() != 0。
2.contains(),containsAll()
用于判断传入的元素、集合是否属于本集合。集合的元素要注意equals方法的编写,内部一般会使用equals方法判断元素是否相等。对于containsAll,只要有一个元素不存在,即返回false。
3.toArray(),toArray(T[])
将集合中的元素,返回到一个数组中,返回的数组引用不能指向集合中的任何实现对象,因此在方法中需要创建一个新的数组返回。这个方法为集合类型和数组类型的API接口提供了一个”桥梁”。
而toArray(T[])是个模板函数,在运行时可以转换为相应类型的数组,在toArray()的基础上,增加了类型转换的能力。
修改操作
1.add(),addAll()
向集合添加元素或者添加传入集合中的所有元素,对于拒绝添加元素的场景,需要返回UnsupportedOperationException,ClassCastException,NullPointerException等runntime异常信息,而添加元素失败会false,调用程序需要判断返回值。
对于addAll()方法,不同的实现类中,返回的逻辑不同,需要针对不同的场景特别对待。
2.remove(),removeAll()
从集合中删除元素,或者删除传入集合中的所有元素,对于拒绝删除元素的场景,需要返回相应的runntime异常信息。如果删除后容器没有变化,需要返回false,调用程序需要根据使用场景判断返回值。
3.retainAll()
取两个集合的交集,如果传入的集合为空,那么最后结果为空。
4.clear()
清除集合中的所有元素
比较和哈希操作
1.hash(),equals()
hash是所以元素的哈希之和。
equals是所有元素相等,对于Map类型,和顺序无关,对于List类型,和顺序有关,根据不同实现会有不同的逻辑,需要根据使用场景处理。
实现介绍
容器的具体实现,根据元素的属性和元素直接的关系,我这里分为三大类:集合、列表、映射。
集合:元素的简单集合,不允许有重复的元素存在,可以无序或者有序
列表:元素的顺序集合,允许有重复的元素存在
映射:每个元素是一个键值对关系,键不允许重复,值允许重复,可以无序或者有序
以上是归纳出的三种基本的容器类型,根据使用的场景,还可以再细分为队列、双端队列、堆栈等。
集合
集合基础接口是set,用于描述一组不重复的元素的集合,对应于数学上的集合抽象,常用于存放某些元素,并且需要判断元素是否已存在在集合中的场景。集合接口提供了基本的集合操作add,remove,contains,size等方法。
常用的不支持并发的实现类有HashSet、TreeSet、LinkedHashSet。这几种集合内部都是基于Map的数据结构存储,将元素存储在键里,值存放一个无用的对象。
HashSet
HashSet基于HashMap实现,因此遍历时无法保证顺序,并且顺序有可能会改变;只允许存在一个null元素。TreeSet
TreeSet基于TreeMap实现,实现了SortedSet接口,提供了一些基于顺序的集合查询操作。这个集合的元素需要实现Comparable接口,在插入的时候排序插入,遍历的时候也是基于元素的顺序遍历;元素不允许为null。LinkedHashSet
LinkedHashSet基于LinkedHashMap实现,遍历时元素顺序可以保证和插入顺序相同;只允许存在一个null元素。示例代码如下:
以下是一个测试的元素类型,修改了常规的hashcode算法,改为取2的余数,因此不同的元素可能会引起哈希冲突。
public class HashRemainderOfTwo implements Comparable<Object>
{
private final int i;
public HashRemainderOfTwo(int i)
{
this.i = i;
}
@Override
public String toString() {
return "" + i + "";
}
/**
* 一个特殊的hashCode,取2的余数,因此不同对象在hash的时候会引起hash冲突
*/
@Override
public int hashCode() {
return i%2;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
HashRemainderOfTwo other = (HashRemainderOfTwo) obj;
if (i != other.i)
return false;
return true;
}
@Override
public int compareTo(Object o) {
HashRemainderOfTwo oo = (HashRemainderOfTwo)o;
return this.i - oo.i;
}
}
对三种Set实现类,按照不同的顺序插入元素,再遍历元素,看输出结果。插入顺序为1,4,2,null。所有的元素hash结果都是0。
public class SetTest
{
static final int hash(Object key)
{
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public static void main(String[] args)
{
HashRemainderOfTwo t1 = new HashRemainderOfTwo(1);
HashRemainderOfTwo t2 = new HashRemainderOfTwo(2);
HashRemainderOfTwo t4 = new HashRemainderOfTwo(4);
Set<HashRemainderOfTwo> set = new HashSet<HashRemainderOfTwo>();
set.add(t1);
set.add(t4);
set.add(t2);
set.add(null);
set.add(null);//只可能有一个null,重复插入无效
System.out.println(set);
Set<HashRemainderOfTwo> tree = new TreeSet<HashRemainderOfTwo>();
tree.add(t1);
tree.add(t4);
tree.add(t2);
// tree.add(null);//TreeSet的元素不允许为null
System.out.println(tree);
Set<HashRemainderOfTwo> linkset = new LinkedHashSet<HashRemainderOfTwo>();
linkset.add(t1);
linkset.add(t4);
linkset.add(t2);
linkset.add(null);
linkset.add(null);//只可能有一个null,重复插入无效
System.out.println(linkset);
}
}
输出结果:
HashSet:[4, 2, null, 1]
TreeSet:[1, 2, 4]
LinkedHashSet:[1, 4, 2, null]
可以看出,HashSet是顺序无关的,而TreeSet以元素大小排序,LinkedHashSet以插入顺序排序。
而对应于上面集中集合,支持并发的集合实现有:
—未完待续,下篇分析列表类型的容器实现—