typora-root-url: iamge

[TOC]

1.集合接口

1.1将集合的接口与实现分离

​ 与现代的数据结构类库的常见情况一样,Java集合类库也将接口(interface)与实现(implementation)分离

​ 简单的说,定义一个集合类要实现的接口,然后再另外一个类中实现这个接口

1.2Java类库中的集合接口和迭代器接口

​ Java类库中,集合类的基本接口是Collection接口,

​ 该接口包含两个基本的方法 boolean add(E element); 和 Iterator iterator();

​ 如果需自己实现Collectionj接口,直接实现Collection无疑是十分麻烦的,所以java类库又提供了一个AbstractCollection类,这个类只需要实现 size ,iterator方法就可以实现了.

​ 比如现在contatins方法已经由超类提供了,如果有更好的实现方法,也可以由子类提供,覆盖父类方法.

​ 迭代器接口:包含三个方法: E next(); boolean hasNext(); void remove();

​ 可以通过next逐个访问集合中的每个元素,但是到了末尾将会抛出一个NoSuchElementException,因此需要在Next之前调用hasNext方法,如果还有多个可供访问的元素将返回Trut;

​ 编译器简单的将 for each 循环翻译为带有迭代器的循环 该循环可以与任何实现了Iterable接口的对象一起工作,这个接口值包含一个方法 返回一个迭代器 Iterator iterator();

​ 因为 Collection接口扩展了Iterable 接口.所以标准类库中的所有集合都可以使用 for each循环

2.具体的集合

​ Java类库提供的具体数据结构还是很有用途的

​ 所谓结构就是组织形式,数据的结构就是数据怎么组织,即怎么描述,怎么在电脑中存储。不同类型的数据,它们的组织形式(数据结构)是不同的,如我们把一个班的学生按照学号排队,可以用“数组”来描述它,而如果要描述一个家族的系谱,从祖先到子子孙孙,开支散叶,则可以用“树”来描述,因为这样的数据组织起来像一颗树。数组和树,在进行插入数据,删除数据等操作时,它们的操作方式是不一样的。如果想编程序,那么必须要了解一些数据结构方面的知识。因为你首先要知道怎么描述数据。

2.1链表(LinkedList)

优点:解决了数组和ArrayList中的增删元素时,后面的元素往前移动造成的资源浪费

缺点:链表不支持随机访问读写,如果要访问第n个元素,就需要从头开始越过n个元素,效率极低,绝对不要使用遍历的方式来遍历链表

链表的出现解决数组和动态的ArrayList类出现的缺陷,因为数组每删除一个元素以后,后面的元素都要向前移动一位,在数组中插入一个元素也是这样.这会造成资源的浪费.

​ LinkedList:链表中的每个对象存放在独立的节点中,每个节点还存放着序列中下一个节点的引用. 所有的链表实际上都是双向连接的,即每个节点还存放这指向前驱结点的引用(上一个节点的引用).从链表中删除一个元素只需要对被删除元素附近的节点更新一下即可.他会改动被删除元素左右两个元素的节点引用, 如果对.listIterator()返回的对象调用add(), 将会按照提供的次序把元素添加到链表中,它们被依次添加到迭代器当前的位置之前

如果迭代器发现他的集合被另一个迭代器修改了,或者是该集合自身的方法修改了,就会抛出一个Concurrent ModificationException异常,在每个迭代器的开始都会检查自己改写操作的计数值,判断是否与集合的改写操作计数值是否相等,否则将抛出ConcurrentModificationException异常

List

public interface List<E> extends Collection<E>
复制代码


List接口最重要的特点在有序(ordered collection)这个关键字上面,实现这个接口的类可以通过整数索引来访问元素。他可以包含重复的元素。除了包含Collection接口的所有方法外,还包括跟索引有关的部分方法:

E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);
复制代码


其中需要引起注意的地方是ListIterator这个类:List接口在Iterator迭代器的基础上提供了另一个迭代器ListIterator,先来看看ListIterator接口的定义:

public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
复制代码


ListIterator接口继承自Iterator接口,所以他们的差异在ListIterator接口新增的功能上:

  • ListIterator可以向后迭代previous()
  • ListIterator可以获取前后索引nextIndex()
  • ListIterator可以添加新值add(E e)
  • ListIterator可以设置新值set(E e)

2.2数组列表(ArrayList)

优点:知道元素的位置可以快速的获取

缺点:删除或者添加新的元素会比较浪费资源

ArrayList 实现了List接口,所以这个集合是一个有序集合.可以通过迭代器或这get,,set访问每个元素

2.3散列集(HashSet)

​ 散列表的底层数据结构是数组称之为桶,每个桶里面放的是链表,链表中的每个节点就是哈希表中的每个元素

它可以快速获取所需要的对象

​ 如果知道大概需要多少元素需要插入到散列表中就可以设置桶数,通常将桶数设置为预计元素个数的0.75-1.5之间,(有研究人员指出最好设置素数可以避免键的聚集) ,如果不知道需要大概多少元素的时候,当散列表太满散列表就需要再散列,就需要创建一个新的散列表,并将所有的元素添加到这个新表中,然后丢弃原来的表. 如果填充因子为0.75(默认值),当表中超过0.75的位置已经填入元素,这个表就会用双倍的桶数自动的进行再散列,对于大多数应用程序来说,装填因子为0.75是比较合适的0 ,(填充因子决定何时对散列表进行再散列)

​ 如果某个元素散列码为76268,并且这个散列表和128个桶, 则这个对象应该保存在第108个桶中(76268%128),取余所得的结果就是这个元素的桶的索引. 如果这个桶中没有其他元素,此时直接将元素插入桶中就行了. 如果桶中第一个节点已经有一个元素了(散列碰撞),它会自动插入到第二个节点, 当这个桶被占满了,这种现象被称为散列冲突,,这时候需要用新对象与 桶中的所有对象进行比较,查看这个对象是否已经存在.

​ 如果要通过散列码取出元素,桶中可能存在多个元素,他们之间将会比较散列码 而后取出相等的 元素

​ 通常我们使用hashCode方法获取一个对象的散列码,在实际应用中我们会遇到不同的散列码转为相同的桶号的情况,这种情况叫做碰撞

​ 桶号=散列码%桶数;

HashSet:实现了基于散列表的集

​ 只有不关心集合中元素的顺序时才使用,它的contains方法已经重新定义了,他只查看某个桶中的所有元素,而不必查看集合中的所有元素

​ 同时如果元素的散列码发生改变,那么元素在散列表中的位置也将发生改变

2.4树集(TreeSet)

​ 优点:树集是一个有序集合,可以以任意顺序将元素插入到集合中,在对集合进行遍历的时候,每个值将自动的按照排序后的顺序呈现(添加到树集的元素他会自动的进行排序)

​ 缺点:将一个元素添加到树中要比添加到散列表中的慢,但是与将元素添加到数组或链表的正确位置上相比还是快很多的.

​ 如果树中包含N个元素,查找新元素的正确位置平均需要log2N次比较

​ 如果一棵树包含了1000个元素,添加一个元素大概需要比较10次,肯定比HashSet慢的

2.5对象的比较

​ TreeSet怎么知道元素怎么排列呢?默认情况下,树集假定插入的元素实现了Comparable接口.如果要插入自定义的对象,就必须通过实现Comparable接口自定义排序顺序.

​ 如果在不同的集合中需要对自定义的对象进行不同的排序,而通常每个对象只有一个CompateTo方法,这时候就可以选择在TreeSet的另一个构造方法中实现Comparator这个接口.


public class TreeSet_demo {
public static void main(String[] args) {
SortedSet<Item> items=new TreeSet<>();
items.add(new Item("asddsa",666));
items.add(new Item("asdd",123));
items.add(new Item("bsa",132));
items.add(new Item("eddsa",324));
items.add(new Item("ggga",633));
items.add(new Item("lfswa",232));
Iterator iterator=items.iterator();
while (iterator.hasNext()){
Item i= (Item) iterator.next();
System.out.println("对数字进行排序"+i.getDescription()+"==="+i.getNum());
}
SortedSet<Item> items1=new TreeSet<>(new Comparator<Item>() {
//需要比较不同的属性时可以在构造器中另外引用一个实现Comparator接口的对象
@Override
public int compare(Item o1, Item o2) {
String a=o1.getDescription();
String b=o2.getDescription();
return a.compareTo(b);
}
});
items1.addAll(items);
iterator =items1.iterator();
while (iterator.hasNext()){
Item i= (Item) iterator.next();
System.out.println("对字符串进行排序"+i.getDescription()+"==="+i.getNum());
}
}
}


//另外自定义类型 以及实现了对该对象以该对象的数字进行排序
package Set_demo;

public class Item implements Comparable<Item>{
private String description;
private int num;

public Item(String description, int num) {
this.description = description;
this.num = num;
}

public String getDescription() {

return description;
}

public void setDescription(String description) {
this.description = description;
}

public int getNum() {
return num;
}

public void setNum(int num) {
this.num = num;
}

@Override
public int compareTo(Item o) {
return Integer.compare(num ,o.getNum());
}
}

复制代码


2.6队列与双端队列

​ 优点:队列可以有效的删除一个头部元素或在尾部添加一个元素 ,双端队列可以让人们有效的在头部和尾部同时添加或删除元素.

​ 缺点:不支持在队列中间添加元素

​ Deque(双端队列) 在Java se6 中引入Deque接口,并由ArrayDeque和LinkedList类实现,这两个类都提供了双端队列,并且在必要时可以增加队列的长度

2.7优先级队列

​ 优先级队列(priority queue): 可以按照任意的顺序插入,却总是按照排序的顺序进行检索,也就是说无论何时调用remove ,总是移除优先级最小的元素

​ 优先级队列使用了一个优雅且高效的数据结构 堆(heap) 堆是一个可以自我调整的二叉树,对树执行add,remove操作可以让最小的元素移动到根,而不必花时间对元素进行排序

​ 和TreeSet一样,一个优先级队列既可以保存实现了Comparable接口的类对象,也可以保存 在优先级队列构造器中提供比较器的 对象

package Set_demo;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.PriorityQueue;

public class PriorityQueue_demo {
public static void main(String[] args) {
PriorityQueue<GregorianCalendar> m=new PriorityQueue<>();
m.add(new GregorianCalendar(1909,Calendar.JULY,9));
m.add(new GregorianCalendar(1907,Calendar.JULY,8));
m.add(new GregorianCalendar(1908,Calendar.JULY,7));
for (GregorianCalendar g:m
) {
System.out.println(g.get(Calendar.YEAR));//按优先级取出元素
}
System.out.println(m.remove().get(Calendar.YEAR)); //将会按照元素的优先级删除元素
}
}

复制代码


2.8映射表(Map)

​ 集是一个集合,它可以快速的查找现有的元素,要查看一个元素,需要有查找元素的精确副本.

​ 通常我们知道某些键的信息,并想要查找与之对应的元素. 映射表(map)数据结构就是为此设计的,映射表用来存放键值对.如果提供了键就能够得到值.

​ java类库为映射表提供了两个通用的实现 HashMap和TreeMap 这两个类都实现了Map接口

​ 散列映射表对键进行散列,树映射表用键的整体顺序对元素进行排序.并将其组织成搜索树

应该选择散列映射表还是树映射表? 与集一样,散列表稍微快一点,如果不需要按照排列顺序访问键,就最好选择散列.

​ 键必须是唯一的,不能对同一个键存放两个值,如果对同一个键调用两次put第二次的值将会覆盖第一次的值并返回上一次的值.

视图对象:

​ 表中有3个视图,键集,值集合,键值对集 (我的理解视图就是把集合里面的东西展示出来,但是不能修改它的内容或者删除,仅仅只是展示而已.)

package map;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMap_demo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("键值1", "值1");
map.put("键值2", "值2");
map.put("键值3", "值3");
map.put("键值4", "值4");
map.put("键值5", "值5");
map.put("键值6", "值6");
System.out.println(map);
map.remove("键值3");
map.put("键值1","被替换的值1");
map.put("键值7", "值7");
for (Map.Entry m: map.entrySet()
) {
//对条目集进行迭代
System.out.println(m.getKey());
System.out.println(m.getValue());
}
for (String s: map.keySet()
) {
//枚举映射表中的所有键
System.out.println(s);
}
}
}

复制代码


2.9专用集与映射表类

​ 1.弱散列映射表

​ 2.链接当散列集和链接映射表

​ 3.枚举集与映射表

​ 4.标识散列映射表

3.集合框架

​ java集合类库构成了,集合类的框架,它为集合的实现者定义了大量的接口和抽象类,并对其中的某些机制给与了描述.通常使用这些集合类不必了解集合框架,但如果向实现御用多种集合类型的泛型算法,或者想要增加新的类型集合,就必须要了解一下集合框架了.


集合框架的接口

集合有两个基本的接口CollectionMap;可以使用 **boolean add(E element)**方法向集合添加插入元素,映射表保存的是键值对所以使用put方法进行插入,如果向读取某个元素,就需要使用迭代器访问他们,也可以使用get方法从映射表读取值

​ V get(K key)

​ List是一个有序集合,元素可以添加到容器的制定位置,将对象放置在某个位置上可以采用两种方式,使用元素索引或使用列表迭代器.List定义了几个用于随机访问的方法:

  void add(int index ,E element);   //在指定位置插入元素

E get(int index);//获取指定位置的元素

void remove(int index);//删除指定位置的元素
复制代码


java集合类中元素的访问分为随机访问和顺序访问。随机访问一般是通过index下标访问,行为类似数组的访问。而顺序访问类似于链表的访问,通常为迭代器遍历。以List接口及其实例为例。ArrayList是典型的随机访问型,而LinkedList则是顺序访问型。List接口既定义了下标访问方法又定义了迭代器方法。所以其实例既可使用下标随机访问也可以使用迭代器进行遍历。但这两种方式的性能差异很明显

同时为了避免执行成本较高的随机访问操作,Java SE1.4 引入了一个标记接口RandomAccess,这个接口没有任何方法,只用来检测一个特定的集合是否支持高效的随机访问

​if(c instanceof RandomAccess){...}​

Set接口和Collection接口是一样的,但是集的方法有着更严格的定义 ,集的add拒绝添加重复的元素 equals方法定义两个集相等的条件是包含相同的元素,但顺序不必相同 hascode方法定义要保证相同的元素可以得到相同的散列码.

​既然两个方法签名相同(方法名字相同,参数也相同),为什么还要定义两个接口?​

​因为并不是所有的集合都是集,建立Set接口后,可以编写仅接受集的方法.​

Java se 1.6引入了 接口 NavigableSet和NavigableMap,其中包含了几个用于在有序集和映射表中查找和遍历的方法.(理论上讲,这几个方法已经包含在 sortedmap和sortedset的接口中),treeset和treemap实现了这几个接口

SortedMapSortedSet接口定义了排序功能,实现了他们的子类都具有接口中定义的功能.


集合框架中的类


同时还有许多java第一版遗留下来的容器类,在集合框架之前就有了的:(Vector,Stack,HashTable,Properties)

以上这些就组成了我们所说的集合框架

3.1视图与包装器

​ 视图的概念:映射表类的keySet方法为例;keySet 方法返回一个实现了Set接口的类对象,粗看起来好像这个方法创建了一个新set并将源映射表的所有键都填充了进去,其实并不是. ketSet方法返回了一个实现Set接口的类对象,这个对象的方法对原映射表进行操作.(只可读取,而不能修改和添加);

​ 1.轻量级集包装器

​ Arrays类的静态方法asList将返回一个包装了java数组的List包装器,这个方法可以将数组返回给一个期望得到列表或者集合变元的方法.

    String[]arr=new String[]{"a","b","c"};
List l=Arrays.asList(arr);
//Collections类包含许多实用的方法,这些方法的参数和返回值都是集合
复制代码


​ 返回的对象并不是ArrayList,它是一个视图对象,带有访问底层数组的get和set方法,改变数组大小的所有方法(例如与迭代器相关的add remove方法)将会抛出一个Unsupported OperationException异常

​ 2.子范围

​ 可以为集合建立子范围视图,假如想从集合中读取第1到3的元素

        List mList=Arrays.asList(arr);
List v=mList.subList(1,4); //他只会取下标为1,2,3的元素
复制代码


​ 3.不可修改的范围

​ 4.同步视图

​ 5.检查视图

​ 6.关于可选操作的说明

3.2批操作

Set<String> set1 = new HashSet<>();
set1.add("asd1");
set1.add("asd2");
set1.add("asd3");
set1.add("asd4");
set1.add("asd5");
Set<String> set2 = new HashSet<>();
set2.add("asd1");
set2.add("asd2");
Set<String>mSet=new HashSet<>(set1); //将传进来的参数保存到初始集合中
mSet.retainAll(set2); //返回mset和set2的交集
for (String s:mSet
) {
System.out.println(s);
}
复制代码


3.3集合与数组之间的转换

      String []arr=new String[]{"a","b","c"};
HashSet<String> mySet=new HashSet<>(Arrays.asList(arr)); //将数组转换为集合
arr=mySet.toArray(new String[mySet.size()]); //将集合转换为数组 []中可以指定大小
复制代码


4.算法

​ 泛型集合接口有一个很大的优点,即算法只需要实现一次.

​ 传统方式下,找出元素的最大元素的代码如下.

//数组列表中
if(a.size()==0) throw new NoSuchElementException();
T largest =v.get(0);
for(int i=0;i<a.size();i++){
if(largest.compareTo(v.get(i))<0);
largest=v.get(i);
}
复制代码
//链表中
if(l.isEmpty()) throw new NoSuchElementException;
Iterator<T> iter=l.iterator();
T largest=iter.next();
while(iter.hasNext){
T next=iter.next();
if(largest.comparaTo(next)<0){
largest=next;
}
}
复制代码


以上方法很多重复的地方,而且也很容易出错,不方便维护

但是如果通过集合接口使用的话,效率将会大大提升.只需要接收任何实现了Collection接口的对象

    public static<T extends Comparable> T max(Collection<T> c){
if(c.isEmpty()) throw new NoSuchElementException();
Iterator<T> iterator=c.iterator();
T max=iterator.next();
while (iterator.hasNext()){
T next=iterator.next();
if(max.compareTo(next)<0){
max=next;
}
}
//返回最大值
return max;
}
复制代码


​ 这样不管它是链表还是数组列表都只需要一个方法就可以实现

4.1排序与混排

​ 简单的升序排序

List<Integer> mlist = new LinkedList<>();
mlist.add(1);
mlist.add(434);
mlist.add(53);
mlist.add(6);
mlist.add(1453);
mlist.add(666);
mlist.add(334);
mlist.add(23);
System.out.println(mlist);
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
mlist.sort(comparator);//按照传入的对象进行排序
System.out.println("升序排序以后");
System.out.println(mlist);
复制代码


Collections的sort方法可以对实现了List的接口的集合进行排序.

        mlist.sort(comparator);//按照传入的对象进行排序
Collections.sort(mlist,comparator); //如果向采用其他方法对列表进行排序,可以将Comparator对象作为第二个对象传入
Collections.sort(mlist);
复制代码


混序和逆序

    System.out.println("降序排序以后");
Collections.sort(mlist, Collections.reverseOrder()); //返回一个比较器,它对实现 Comparable接口的对象集合施加了 自然排序的相反。 (就是降序排序)
Collections.sort(mlist, Collections.reverseOrder(comparator));//返回一个比较器,它强制指定比较器的反向排序。
//以上两个方法的效果相同,都是按照降序排序
System.out.println(mlist);


List<Integer> list = new ArrayList<>();
for (int i = 0; i < 49; i++) {
list.add(i);
}
Collections.shuffle(list); //对集合中的元素进行混乱排序
List<Integer> subList = list.subList(0, 6);//截取list的子集
Collections.sort(subList); //对子集进行升序排序
System.out.println("指定子集排序");
System.out.println(subList);
复制代码


4.2二分查找

​ Collections.binartSearch(...)

​ 只有采用随机访问,二分查找才有意义,如果必须使用迭代方式来遍历链表中的一半元素来找到中间位置的元素,二分查找就完全失去了优势.

4.3简单算法

​ Collestions内置了许多简单的算法,如查找最大元素,将一个列表的元素复制到另一个例表,二分查找等. 因为这些算法很多人使用,在阅读源代码的时候如果每个人都自己实现那就要一直看源代码,如果内置了这个简单的算法那就只需要知道类名和方法名就可以知道该功能了.

4.4编写自己的算法

​ 如果要编写自己的算法(以集合为参数),应该尽可能的使用接口,而不要使用具体的实现类,因为可以接收一个更加通用的集合,比一个具体的具有更加方便,调用者可以使用任意集合调用该方法.

5.遗留的集合

​ java问世以来就存在的集合类:Hashtable,Properties,Vector,Stack,BitSet

5.1Hashtable类

5.2枚举

5.3属性映射表

5.4栈

5.5位集

​ 测试编译程序性能的一种流行基准