9.1 java集合框架
一 将集合的接口与实现分离
例如:队列接口的可能实现形式:
public interface Queue<E>
{
void add(E element);
E remove();
int size();
}
队列的两种实现形式:一种使用循环数组,一种是使用链表
*如果需要一个循环数组队列,就可以使用ArrayDeque类,如果需要一个链表队列,就直接使用LinkedList类,这个类实现了Queue接口。
一旦构建了集合就不需要知道究竟使用了哪种实现
使用接口类型存放集合的引用:
Queue<Customer> expressLane = new CircularArrayQueue<>(100);
expressLane.add(new Customer("tiffany"));
Queue<Customer> expressLane = new LinkedListQueue<>(100);
expressLane.add(new Customer("tiffany"));
循环数组是一个有界集合,如果程序中要收集的对象数量没有上限,就最好用链表来实现
二 Collecton接口(集合类的基本接口)
public interface Collection<E>
{
boolean add(E element);//向集合中添加元素
Iterator<E> iterator();//用于返回一个实现了Iterator接口的对象
...
}
三 迭代器
public interface Iterator<E>
{
E next();//访问集合中的每个元素,迭代器越过下一个元素,并返回刚刚越过的那个元素的引用
boolean hasNext();//判断是否有下一个元素(存在可访问的元素),就返回true
void move();//删除上次调用next方法时返回的元素
default void forEachRemaining(Consumer<? super E> action);
}
“for each”循环是带有迭代器的循环,任何集合都可以使用它
元素被访问的顺序取决于集合类型
查找一个元素的唯一方法就是调用next
如果想要删除指定位置上的元素,仍然要越过这个元素,例如
Iterator<String> it = c.iterator();//调用迭代器
it.next();//越过第一个元素
it.move();//删除它
ps:如果调用remove 之前没有调用next将是不合法的。
四 泛型使用方法
Collection接口声明的一些方法:
int size()返回当前存储在集合中的元素个数
boolean isEmpty()如果集合中没有元素就返回true
boolean contains(Object obj)如果集合中包含了一个与obj相等的对象,就返回true
boolean containsAll(Collection<?> other)如果这个集合包含other集合中的所有元素,就返回true
boolean add(Object element)将一个元素添加到集合中
。。。
五 集合框架中的接口
集合有两个基本接口:Collection和Map
List集合:有序,访问方式:使用迭代器(顺序访问),随机访问
Set集合:无序,不允许增加重复元素
9.2 具体的集合
除以Map结尾的类之外,其他类都实现了Collection接口,而以Map结尾的类实现了Map接口。
一 链表(有序集合)
1 数组:优点:查询修改快 缺点:增删慢
链表:优点:增删快 缺点:查询修改慢
2将三个元素中第二个元素删除
List<String> staff = new LinkedList<>();
staff.add("a");
staff.add("b");
staff.add("c");
Iterator iter = staff.iterator();
String first = iter.next();
String second = iter.next();
iter.remove();
3LinkedList.add:void类型,它假设添加操作总会改变链表(与Collection.add不同)
4用迭代器添加元素,迭代器总是在当前位置之前
5如果迭代器发现它的集合被另一个迭代器修改了,或是被该集合自身的方法修改了,就会抛出一个异常
List<String> list = ...;
ListIterator<String> iter1 = list.listIterator();
ListIterator<String> iter2 = list.listIterator();
iter1.next();
iter1.remove();
iter2.next();//error
由于iter2检测出这个链表被从外部修改了
为了避免这种异常,可以根据需要给容器附加许多迭代器,但是这些迭代器只能读取列表;另外再单独附加一个既能读又能写的迭代器。
6使用链表的优点:尽可能地减少在列表中间插入或者删除所付出的代价
7如果需要对集合进行随机访问,就使用数组或ArrayList,而不要使用LinkedList
二 数组列表
两种访问元素的协议:
-
迭代器
-
使用get set方法随机地访问每个元素(不适用于链表)
ps:需要动态数组时使用ArrayList而不使用Vector的原因:Vector类的所有方法都是同步的,可以由两个线程安全滴访问一个Vector。但是如果由一个线程访问Vector,代码要在同步操作上耗费大量的时间。而ArrayList不是同步的。所以如果不需要同步时使用ArrayList,不要使用Vector
三 散列集(链表数组实现:因为数组是无序的)
1 散列表为每个对象计算一个整数,称为散列码。
2 补P170hashCode方法(定义在Object类):字符串的散列码是由内容导出的;而在StringBuffer类中没有定义hashCode方法,返回的是对象存储的地址
如果重新定义equals方法,就必须重新定义hashCode方法,自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)为true,a与b必须具有相同的散列码
3 散列冲突:桶被占满(散列值相同)
桶数是指用于收集具有相同散列值的桶的数目,如果大致知道会有多少元素要插入到散列表中,就可以设置桶数,一般将桶数设置为预计元素个数的75%-150%,最好是素数
4 如果散列表太满,就需要对散列表再散列,一般填装因子大于0.75时就需要对散列表进行再散列
5 散列集的应用:HashSet类,访问顺序是随机的
四 树集(TreeSet)
1.树集是一个有序集合
2.补充:树的基本概念和存储:树是具有n个结点的集合,每个结点最多只有一个前驱,但可以有多个后继。(根结点无前驱)没有后继的结点称为叶子或终端结点。
一个结点的后继称为该结点的孩子,孩子部分顺序;一个结点的前驱称为该结点的双亲
树的表示方法:
-
广义表表示法
-
双亲表示法
-
左孩子右兄弟
-
孩子链表表示法