集合
本文主要介绍一下Java中几个集合的框架。
Collection接口
在Java类库中,集合类的基本接口是Collection接口。这个接口有两个基本方法:
public interface Collection<E>
{
boolean add(E element);
Iterator<E> iterator();
}
add方法是向集合中添加元素。如果改变了集合,方法返回值是true,否则返回false。
iterator方法则会返回一个实现Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。
Iterator迭代器
迭代器就是一个对集合进行引用的工具。对于数组我们可以通过下标来对其中元素进行引用。部分集合不支持这种引用,这就需要构造器。
Iterator接口中主要有三个方法:
public interface Iterator<E>
{
E next();
boolean hasNext();
void remove();
}
Iterator的简单使用:
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("11");
Iterator it = arrayList.iterator();
System.out.println(it.next());
//打印输出11
如果我们需要遍历整个集合,我们则需要hasNext方法来判断时候到达了集合的末尾。当到达了集合的末尾,hasNext会返回false。
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("11");
arrayList.add("22");
Iterator it = arrayList.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
//打印输出11 22
如果我们只想遍历集合中的元素,我们可以使用for each循环:
for(String s : arrayList)
{
System.out.println(s);
}
Iterator接口的remove方法将会删除上次调用next方法时返回的元素。我们可以先next返回该元素,然后remove删除。
集合框架
java集合有两个基本接口:Collection和Map。(图片源于网络)
Collection类下面分为List,Set和Queue(不做介绍)
List接口
元素存取有序,即先存先取
允许有重复的元素
有索引值,可以使用for循环
List接口的常用方法:
void add(int index,E element)
void remove(int index)
E get(int index)
E set(int index,E element)
也可以使用迭代器来访问。
由数组支持的有序集合可以快速地随机访问,因此适合使用List方法并提供一个整数索引来访问,如ArrayList。而链表支持的有序结合尽管也是有序的,但是通过索引访问很慢,所以最好使用迭代器来遍历,如LinkedList。
ArrayList在前面我们介绍过,所以接下来介绍一下LinkedList。
在数据结构中,我们学过链表。每个节点保存着自己的信息和对下一个节点的引用。在Java中,所有链表实际上都是双向链接的——即每个节点还存放着指向前去节点的引用。
LinkedList方法
public static void main(String[] args){
LinkedList<String> l = new LinkedList<>();
l.add("amy"); //向链表末尾添加
l.add(1,"tom"); //向链表1号索引位置插入
l.add("john");
l.get(1); //返回1号索引位置的元素
l.getFirst(); //返回链表第一个元素
l.getLast(); //返回链表最后一个元素
l.remove(); //移除表头元素
l.remove("john"); //移除指定的某个元素
l.remove(1); //移除1号索引位置的元素
l.removeFirst(); //移除表头元素
l.removeLast(); //移除表尾元素
}
当我们插入删除链表元素时,其他元素的索引位置也会相对改变
Set接口
不允许有重复的元素
没有元素索引,不能for循环
TreeSet和HashSet是存取无序的
LinkedHashSet是存取有序的
有一种众所周知的数据结构们可以快速地查找所需要地对象,这就是散列表(hash table)。散列表为每一个对象计算一个整数,成为散列码(hash code)。散列码是由对象的实例域产生的一个整数。更准确地说,具有不同数据域的对象将产生不同的散列码。
在Java中,散列表采用链表数组实现。每个链表被称为桶。每一个散列码对应一个桶。若出现多个元素对应一个桶,这种现象被称为散列冲突,这时候会将多个元素按照链表给连接起来。
在Java 8中,一个桶超过8个元素会从链表变为平衡二叉树。这样当产生冲突的元素过多时,能提高查找的性能
HashSet
HashSet就是通过hashcode来确定元素的位置,一个hash code上可以存储多个元素。HashSet通过hashcode和equals方法可以快速查看元素是否在集合中重复。
当你试图把对象加入HashSet时,HashSet会使用对象的hashcode来判断对象加入的位置。同时也会与其他已经加入的对象的hashcode进行比较,如果没有相等的hashcode,HashSet就会假设对象没有重复出现。
如果对象的hashCode值是不同的,那么HashSet会认为对象是不可能相等的。
因此我们自定义类的时候需要重写hashCode,来确保对象具有相同的hashCode值。
如果元素(对象)的hashCode值相同,是不是就无法存入HashSet中了? 当然不是,会继续使用equals 进行比较.如果 equals为true 那么HashSet认为新加入的对象重复了,所以加入失败。如果equals 为false那么HashSet 认为新加入的对象没有重复.新元素可以存入。
public static void main(String[] args){
HashSet<String> set = new HashSet<>();
set.add("amy");
set.add("tom");
set.add("amy");
Iterator<String> it = set.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
//打印tom amy
从打印结果我们可以发现,HashSet是存取无序的,而且会默认地去掉重复地元素。如果我们现在需要按照一定的顺序存取元素,并且要求去重,这时候就需要TreeSet了。
TreeSet
TreeSet与HashSet很类似,不过它是一个有序集合。在对集合进行遍历时,每个值将自动地按照排序后地顺序实现。TreeSet底层是通过红黑树实现的。
TreeSet自定义排序可以通过两个方式来实现:
- 让元素自身具备比较性,即元素实现Comparable接口,重写compareTo方法
- 让容器自身具备比较性,即定义一个类实现Comparator接口,覆盖compare方法,并将该接口的子类对象作为参数传递给TreeSet集合的构造函数。
要使用TreeSet,必须能够比较元素。这些元素必须实现Comparable接口,或者构造集时必须提供一个Comparator
LinkedHashSet
LinkedHashSet底层则是链表+散列表实现的,存取有序。可以看作散列表按照链表插入顺序来存取。
Map
在Set接口中,我们通常使用hashcode作为元素的索引值。但有时候我们已经有要查找的元素的索引了。这时候我们可以用映射来存放键值对。如果我们知道了键,就可以查找到相对应的值。Java中提供了HashMap和TreeMap两种框架。
HashMap对键进行散列,TreeMap则用键的排序顺序来对值进行排序,并将其组织成搜索树。散列或比较函数只能作用于键,与键关联的值不能进行散列或者比较。
HashMap和TreeMap的特点与HashSet和TreeSet一样。HashMap查找快,TreeMap则会按照排列顺序访问键。
基本映射操作
public class Launch {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "amy");
map.put(2, "tom");
map.put(3, "john");
System.out.println(map.get(1));
}
}
在创建map的时候,我们需要定义映射中键值对的数据类型。put方法将键值对插入到映射中。如果这个键已经存在,那么新的值则会取代旧的值。
get方法则会返回与键对应的值。如果映射中没有这个键则会返回null。
map的遍历
对于map的遍历,我们首先要获得map的键集合,通过不断获得键,来找到值。
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "amy");
map.put(2, "tom");
map.put(3, "john");
Set<Integer> set = map.keySet();
Iterator<Integer> it = set.iterator();
while(it.hasNext())
{
int i = it.next();
System.out.println(i+" "+map.get(i));
}
}
通过keySet方法返回一个存储键的集合,然后遍历集合即可找到值。也可以使用for each循环
for(int i : set)
{
System.out.println(i+" "+map.get(i));
}
Map类中提供了一个同时反映键与值的方法entrySet():
Set<Map.Entry<Integer, String>> entries = map.entrySet();
该方法会返回一个保存着键值对的一个集合。Map.Entry接口提供了getKey和getValue方法:
for(Map.Entry<Integer, String> entry: map.entrySet())
{
System.out.println(entry.getKey()+" "+entry.getValue());
}