1.Java集合框架
java.util包中提供了一些集合类,这些集合类又被称为容器。集合类与数组的不同之处是,数组的长度是固定的,集合的长度是可变的;数组用来存放基本数据类型和引用数据类型的数据,集合只能用来存放引用数据类型的数据。常用的集合有List集合、Set集合和Map集合。
Map的实现类中常用的类有HashMap、TreeMap、HashTable(线程安全)。
2.Collection接口的常用功能
由于Collection接口不能进行实例化,所有以下的测试使用多态,即使用它的实现子类。以下的介绍中包含了泛型的一些知识。
注:Collection接口的实现类一般都重写了toString方法。
①添加功能
boolean add(E e)
boolean addAll(Collection extends E> c)
List l=new ArrayList();
System.out.println(l); //[]
System.out.println(l.add("haha")); //true
System.out.println(l); //[haha]
注:add方法的源码显示此方法一直返回true,即添加功能永远成功。
addAll用来添加另一个集合中的所有元素。例,
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
System.out.println(l2.addAll(l)); //true
System.out.println(l); //[hello, world, java]
System.out.println(l2); //[hello, hello, world, java]
②删除功能
void clear()
boolean remove(Object o)
removeAll(Collection> c)
clear方法删除集合中的所有元素,remove方法移除指定元素。
List l=new ArrayList();
System.out.println(l);
System.out.println(l.add("haha"));
System.out.println(l);
l.add("java");
System.out.println(l); //[haha, java]
System.out.println(l.remove("haha")); //true
System.out.println(l); //[java]
l.clear();
System.out.println(l); //[]
removeAll移除另一个集合中与本集合相同的元素,只要移除一个就返回true。例,
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("haha");
System.out.println(l2.removeAll(l)); //true
System.out.println(l); //[hello, world, java]
System.out.println(l2); //[haha]
③判断功能
boolean contains(Object o)
boolean containsAll(Collection> c)
boolean isEmpty()
contains判断指定元素在集合中是否存在,底层是用equals方法来判断是否存在;isEmpty判断集合是否为空。
List l=new ArrayList();
l.add("haha");
l.add("java");
System.out.println(l); //[haha, java]
System.out.println(l.contains("java")); //true
System.out.println(l.contains("hello")); //false
System.out.println(l.isEmpty()); //false
l.clear();
System.out.println(l.isEmpty()); //true
containsAll判断本集合中是否包含指定集合的所有元素,只有全部包含才返回true。例,
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("haha");
System.out.println(l.containsAll(l2)); //false
l2.remove("haha");
System.out.println(l.containsAll(l2)); //true
④迭代器
Iterator< E> iterator()
iterator是集合的专属遍历方法,方法返回Iterator接口的实现类对象(内部类),这里是多态。
Iterator接口的方法:
List<String> l=new ArrayList<>();
l.add("hello");
l.add("world");
l.add("java");
Iterator<String> it = l.iterator();
while(it.hasNext()) {
System.out.print(it.next()+" "); //hello world java
}
for(Iterator<String> it2 = l.iterator();it2.hasNext();) {
System.out.print(it2.next()+" "); //hello world java
}
虽然while循环结构清晰简单,但是建议使用for循环。因为for循环的it2变量在循环结束后就销毁了,节省了内存,提高了效率。
注:不要在一条语句中多次使用next方法,因为每次使用next方法都会移动到下一个元素的位置,很容易出现NoSuchElementException异常。
⑤长度
int size()
size获取集合的长度。例,
List l=new ArrayList();
l.add("haha");
l.add("java");
System.out.println(l.size()); //2
⑥集合转数组
Object[] toArray()
toArray将集合转为了Object类型的数组。例,
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
Object[] o = l.toArray();
String s=(String)o[0];
System.out.println(o[0]+" "+s.length()); //hello 5
String s2=(String)o[1];
System.out.println(o[1]+" "+s2.length()); //world 5
System.out.println(((String)o[0]).length()); //5
注:((String)o[0]).length()
与(String)o[0].length()
是不同的,前者将o[0]转为String类型后调用length方法,后者将o[0]调用length方法后的结果转为String类型,但是o[0]是Object类型的,无法调用String类型的特有方法length,所有此时会报错。
⑦其他功能
boolean retainAll(Collection> c)
retainAll判断本集合与指定集合是否有交集(相同的元素),无论是否有都将结果保存在方法调用者即本集合中(有交集将交集放在本集合中,无交集则本集合为空),返回值为判断本集合是否发生过改变。例,
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("haha");
System.out.println(l.retainAll(l2)); //true
System.out.println(l); //[hello]
System.out.println(l2); //[hello, haha]
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("haha");
System.out.println(l.retainAll(l2)); //true
System.out.println(l); //[] 没有交集,可以看做空集也是一种结果
System.out.println(l2); //[haha]
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
List l2=new ArrayList();
l2.add("hello");
l2.add("world");
l2.add("java");
System.out.println(l.retainAll(l2)); //false
System.out.println(l); //[hello, world, java] 集合没有发生改变,返回false
System.out.println(l2); //[hello, world, java]
3.List集合
①概述
List是一个接口,在它的实现类中常用的类有ArrayList、LinkedList、Vector(线程安全)。List接口及其子类是有顺序的(注意不是排序)允许重复的,可以通过索引查找元素。
数组和数组列表有一个重大的缺陷即在数组的中间位置插入或删除一个元素很麻烦,要插入位置之后的元素集体要向后移动一个位置,要删除位置之后的元素集体要向前移动一个位置。链表解决了这个问题。链表将每个对象存放在独立的结点中,每个结点还存放着序列中下一个结点的引用。在Java中,所有的链表都是双向链表,每个结点还存放着指向前驱结点的引用。
②特有功能
因为List接口是Collection接口的子接口,所以Collection的常用方法List及其子类都能使用。
void add(int index,E element)
add方法在集合中的index位置插入element元素。
注:index只能是已添加的所有元素的长度+1或长度内,超过会出现异常。
E get(int index)
get方法获取index位置的元素,注意index不要越界。
E remove(int index)
remove删除index位置的元素,注意index不要越界。
E set(int index,E element)
set方法将index位置的元素替换为element并返回被替换掉的元素。例,
List l=new ArrayList();
l.add("hello");
l.add("world");
l.add("java");
System.out.println(l.set(1,"haha")); //world
System.out.println(l); //[hello, haha, java]
ListIterator listIterator()
List集合的特有迭代器方法,返回一个ListIterator类型的对象,实际返回ListIterator接口实现类的对象,此处使用了多态。
③ListIterator特有方法
ListIterator继承了Iterator接口,所以Iterator的方法ListIterator及其实现类都可以使用。
E previous()
boolean hasPrevious()
previous方法获取上一个元素,hasPrevious和hasNext的使用相似。
注:使用previous前必须首先使用next方法,否则previous方法无意义(因为迭代器默认是从起始元素开始)。
④要点
不能在迭代器遍历元素时修改元素
如果修改会出现并发修改异常ConcurrentModificationException,因为迭代器是依赖于集合存在的,当集合修改元素时迭代器却不知道,所以会出现异常。解决方法:
第一种解决方法使用的是ListIterator的特有方法:void add(E e),此时元素在刚刚遍历的元素的后面。第二种解决方法添加的元素在集合的最后面添加。
ArrayList特点
底层数据结构是数组,查询快,增删慢,线程不安全所以效率高
Vector特点
底层数据结构是数组,查询快,增删慢,线程安全所以效率低
LinkedList特点
底层数据结构是链表,查询慢,增删快,线程不安全所以效率高
注:这三个常用类是List接口的实现类,所以Collection接口的方法和List接口的方法以上三个实现类都可以使用。
注意remove方法的使用
用remove方法清除一个List集合中的元素后,此元素后面的元素会向前进一位,在某些特殊算法中注意不要将进位的元素漏掉。
集合中的元素可以是集合
⑤LinkedList特有方法
添加:
public void addFirst(E e)
public void addLast(E e)
获取:
public E getFirst()
public E getLast()
删除:
public E removeFirst()
public E removeLast()
添加方法为在开头和结尾添加元素,获取方法为获取开头和结尾的元素,删除方法为删除开头和结尾的元素。例,
LinkedList l=new LinkedList();
l.add("hello");
l.add("world");
l.add("java");
l.addFirst("qiong");
l.addLast("qiong");
System.out.println(l); //[qiong, hello, world, java, qiong]
System.out.println(l.getFirst()); //qiong
System.out.println(l.getLast()); //qiong
System.out.println(l); //[qiong, hello, world, java, qiong]
l.removeFirst();
l.removeLast();
System.out.println(l); //[hello, world, java]
注:使用LinkedList的特有方法时不能将变量声明为List类型的,即List l=new LinkedList();
,因为此时的特有方法不满足多态的条件。
4.增强for
①格式
for(元素的数据类型 变量名:数组或Collection集合) {
对该变量的使用(如,输出),此变量就是数组或集合中遍历的一个个元素;
}
②使用
List<String> s=new ArrayList<String>();
s.add("hello");
s.add("world");
s.add("java");
s.add("haha");
s.add("SilverBullet");
for(String s2:s) {
System.out.print(s2+" "); //hello world java haha SilverBullet
}
③缺点
因为增强for在底层使用时会调用数组或集合的方法对变量进行判断,所以增强for中的数组或Collection集合不能为null,一旦为null,会出现空指针异常NullPointerException。例,
List<String> s=null;
for(String s2:s) {
System.out.println(s2); //Exception in thread "main" java.lang.NullPointerException
}
解决办法:对增强for的目标数组或集合先进行非null判断。
增强for的本质是Iterator,所以与Iterator一样不要在增强for遍历元素时修改元素,一旦修改会出现ConcurrentModificationException并发修改异常。
5.Set集合
①概述
Set是一个接口,它的父接口是Collection,在它的实现类中常用的类有HashSet、TreeSet。Set接口及其子类是无序(即存储顺序和取出顺序不一致)不允许重复的。
②HashSet的add方法
在使用Set接口的实现类的add方法添加元素时,有一些相同的元素并没有添加进集合,此时判断相同的元素的依据是:
重写equals方法的三步骤:
③特殊Set实现类
LinkedHashSet是一个有序不允许重复的类,它是哈希表和链表的结合,它所有的方法都是HashSet类或父接口的方法。
④TreeSet
TreeSet的底层是由TreeMap实现的。它能够对元素以某种规则进行排序,排序主要有两种:自然排序和比较器排序。自然排序即按照从小到大的顺序进行排序,比较器排序即根据创建Set时提供的Comparator进行排序,具体取决于使用的构造方法。
6.TreeSet
①构造方法
public TreeSet()
public TreeSet(Comparator super E> comparator)
第一个构造方法是自然排序,它的底层使用了Comparable接口,它允许添加的实现了Comparable接口的元素进行自然排序,这调用了添加元素的int compareTo(T o)
方法,Comparable是java.lang包下的接口。
2.具体的集合
③散列集
有一种数据结构可以快速地查找所需要的对象,这就是散列表。散列表为每个对象计算一个整数,即散列码。如果自定义类,就需要负责实现这个类的hashCode方法,这用来产生散列码。
注:自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)为true,则a和b必须具有相同的散列码。
在Java中,散列表用链表数组实现。每个列表被称为桶。在Java SE8中,桶满时会从链表变为平衡二叉树。
散列表用于实现几个重要的数据结构,其中最简单的是set类型。
Java集合类库提供了一个HashSet类,它实现了基于散列表的集。散列集迭代器将依次访问所有的桶。只有不关心集合中元素的顺序时才应该使用HashSet。此类允许使用null元素。添加到由HashSet类实现的Set集合中的对象,需要重新实现equals()方法,从而保证插入集合中对象的标识的唯一性。
注:如果元素的散列码发生了变化,元素在数据结构中的位置也会发生变化。
④树集
TreeSet类与散列集十分类似,但它有所改进。树集是一个有序集合。在对树集进行遍历时,每个值都将自动地按照排序后的顺序呈现,每次将一个元素添加到树中时,都被放置在正确的排序位置上。
将一个元素添加到树中要比添加到散列表中慢。
要使用树集必须能够比较元素。这些元素必须实现Comparable接口或者构造树集时必须提供一个Comparator。
树的排序必须是全序。
方法来源:
java.util.TreeSet
java.util.SortedSet
java.util.NavigableSet
⑤队列与双端队列
队列可以有效地在尾部添加一个元素,在头部删除一个元素。有两个端头的队列叫双端队列,它可以有效地在头部和尾部同时添加或删除元素。不支持在队列中间添加元素。
方法来源:
java.util.Queue
java.util.Deque
java.util.ArrayDeque
⑥优先级队列
优先级队列中的元素可以按照任意的顺序插入,但总是按照排序的顺序进行检索。优先级队列并没有对所有的元素进行排序。优先级队列使用了一个高效的数据结构叫做堆。堆是一个可以自我调整的二叉树。
使用优先级队列的典型示例是任务调度。
方法来源:
3.映射
有一种数据结构叫映射,它用来存放键/值对,如果提供了键,就能够查找到值。
Java类库为映射提供了2个通用的实现:HashMap和TreeMap,它们都实现了Map接口。Map没有继承Collection接口,其提供的是key到value的映射。
Map接口的常用方法:
要使用Map集合,通常情况下声明为Map类型,之后通过Map接口的实现类来进行实例化。
散列映射对键进行散列,树映射用键的整体顺序对元素进行排序,并将其组织成搜索树。散列或比较函数只能作用于键,与键关联的值不能进行散列或比较。散列稍微快一些,如果不需要按照排列顺序访问键,最好选择散列。
键必须是唯一的。
①HashMap类
HashMap是基于哈希表的Map接口实现。HashMap通过哈希码对其内部的映射关系进行快速查找。此实现提供所有可选的映射操作,并允许使用null值和null键。但必须保证键的唯一性。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
②TreeMap类
TreeMap中的映射关系存在一定的顺序。如果希望Map集合中的对象存在一定的顺序,应该使用TreeMap类实现Map集合。否则建议使用HashMap实现类实现Map集合,因为由HashMap类实现的Map集合对于添加和删除映射关系效率更高。
TreeMap类不仅实现了Map接口,还实现了java.util.SortedMap接口。因此集合中的映射关系具有一定的顺序。但在添加、删除和定位映射关系上,TreeMap类比HashMap类的性能差一些。由于TreeMap类实现的Map集合中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键对象是null。
方法来源:
java.util.Map
java.util.HashMap
java.util.TreeMap
java.util.SortedMap
4.算法
泛型集合接口有一个很大的优点即算法只需要实现一次。
Collections类中的sort方法可以对实现了List接口的集合进行排序。如果想按照降序对列表进行排序可以使用一种静态方法Collections.reverseOrder()