Java集合容器介绍:
分两大类:Collection,Map
Queue接口也继承了Collection接口,有两种实现方式:
1)循环数组 ArrayDeque
2)链表形式 直接使用LinkedList就行
1、Iterator接口
Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。
也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。
2、Collection (集合的最大接口)继承关系
——List 可以存放重复的内容
——Set 不能存放重复的内容,所以的重复内容靠hashCode()和equals()两个方法区分
——Queue 队列接口
——SortedSet 可以对集合中的数据进行排序
3、List的常用子类
1)ArrayList
底层是数组队列,相当于动态数组。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。
//初始化
List<Integer> indegree = new ArrayList<>();
for (int i = 0; i < graph.size(); i++) {//初始化为0
indegree.add(0);
}
//ArrayList转int[]
// Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,即传递Integer的valueOf函数
//mapToInt方法参数是函数式接口,可以传递函数,用于映射每个元素到对应的结果,将Integer类型的Stream转为IntStream,IntStream自带的toArray()转为int[]
//mapToInt方法参数就是将对应的元素映射为int类型
//(i) -> i会将Integer的i自动拆箱为int类型
int[] res1 = indegree.stream().mapToInt((i) -> i).toArray();
int[] res1 = res.stream().mapToInt(Integer::valueOf).toArray();
//int[]转ArrayList
int[] tt = {1,2,3,4};
//IntStream是int类型的流。stream<Integer> 是Integer类型的流
//IntStream是存的是int类型的stream,⽽Steam是⼀个存了Integer的stream。boxed的作⽤就是将int类型的stream转成了Integer类型的Stream。
//collect使用 Collector对此流的元素进行归纳,放入对应容器中
List<Integer> collect = Arrays.stream(tt).boxed().collect(Collectors.toList());
// ArrayList<int[]> 转 int[][]
ArrayList<int[]> res = new ArrayList<int[]>();
int[][] rr = new int[2][2];
res.toArray(rr); //int[]算对象,如果是int数组则不行
ArrayList<Integer> integers = new ArrayList<Integer>();
int[] hh = new int[22];
integers.toArray(hh); //会报错,因为int不是Object
1)当前数组是由默认构造方法生成的空数组,并且第一次添加数据,默认扩充大小是10(0->10)
2)一般是oldCapacity + (oldCapacity >> 1),相当于扩容1.5倍。
3)当扩充1.5倍不够时,则newCapacity = minCapacity,扩充为需要的大小
4)如果我们大概知道元素的个数,优先考虑List<Person> list2 = new ArrayList<>(50)以指定个数的方式去构造,这样可以避免底层数组的多次拷贝。
5)但指定大小new ArrayList<>(0)为0,前4次扩充都是+1
第5)条原因:
1)不用指定大小new,默认为DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组
2)指定大小为0, 为EMPTY_ELEMENTDATA数组
只有数组为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,才会进行(minsize, 10)的比较
ArrayList 中的操作不是线程安全的,单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
2)LinkedList
LinkedList底层的**链表结构(内部使用Node数据结构实现链表结构)**使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性。
链表的实现:LinkedList类中的属性first,last节点代表链表的入口
transient Node<E> first;
transient Node<E> last;
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
3) vector,stack,queue
vector是同步版的ArrayList,
queue都是基于ArrayList或者LinkedList实现的,
Stack 是继承Vector的,是类,而不是接口
Queue q = new LinkedList<>(); //链队
Queue q1 = new ArrayDeque<>(); // 数组队列,遍历更快,不要寻址
//虽然ArrayDeque底层用数组实现,但其不开放随机访问的接口,所以如果要随机访问,需要转到数组进行操作
Stack stack = new Stack(); // 数组栈
Deque deque = new LinkedList() //双端队列
链栈可以自己手动实现,自定义节点类即可,也可以继承LinkedList,添加一些栈常用方法
4)PriorityQueue
PriorityQueue是类,其作用是每次以O(1)取出队列中权值最小的元素,再以O(log)维护队列
public class PriorityQueue<E> extends AbstractQueue<E>
PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。(不指定Comparator时默认为最小堆)。
有一个用户类Customer,它没有提供任何类型的排序。当我们用它建立优先队列时,应该为其提供一个比较器对象。
//优先队列自然排序示例
Queue<Integer> integerPriorityQueue = new PriorityQueue<>(7);
//匿名Comparator实现
public static Comparator<Customer> idComparator = new Comparator<Customer>(){
// 自定义降序规则,构建大顶堆,头是最大元素
@Override
public int compare(Customer c1, Customer c2) {
return (int) (c1.getId() - c2.getId());
}
};
// 返回-1(负数),第一个参数放前面
// 例子1: if(o1>o2) return -1; 代表o1放前面,而o1>o2,所以是倒序
// 例子2: return (int) (c1.getId() - c2.getId());
//这个代表,当c1<c2时,返回负数,代表c1在前,而c1<c2,故是升序
//优先队列使用示例
Queue<Customer> customerPriorityQueue = new PriorityQueue<>(7, idComparator);
4、Set的常用子类
1)HashSet
无序,唯一,基于 HashMap 实现的,底层采用 HashMap 来保存元素,
HashMap<T, Object>存储数据
2)TreeSet
有序,唯一,通过compareTo或者compare方法来保证元素的唯一性,元素以二叉树的形式存放,红黑树(自平衡的排序二叉树。)
3)LinkedHashSet
其内部是通过 LinkedHashMap 来实现的。
5 Map的常用子类
1)HashMap
HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)((将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树))时,将链表转化为红黑树,以减少搜索时间。
获取HashMap.get(key) 的时间复杂度为O(size Of Key),因为计算key的hashCode需要这些时间
//内部有Node类(kv两个属性),数组是Node数组,每个元素是Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
...
}
transient Node<K,V>[] table;
HashMap的put函数原理图
当使用自定义类,作为hashmap的key时,需要对自定义类重写hashcode和equals方法
//1.计算对象的Hash Code,找到对应的数组下标,获取链表---------------------第一步就是要用到hashCode()方法
//
//2.遍历链表检查对应Hash Code位置中的Key 对象和当前Key对象是否相等,相等的话用返回对应的Value--------------第二步就是要用到equals()方法
2)LinkedHashMap
它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。
它比HashMap多了两个属性:
//链表的头结点
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
//该属性指取得键值对的方式,是个布尔值,false表示插入顺序,true表示访问顺序,也就是访问次数.
private final boolean accessOrder;
//默认都是采用插入顺序来维持取出键值对的次序
LinkedHashMap和HashMap的区别在于他们的基本数据机构上,我们来看一下LinkedHashMap的基本数据结构Entry:
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);
}
next是用于维护HashMap指定table位置上连接的Entry顺序的;
before、after是用于维护Entry插入的先后顺序的.
正是因为before、after和head,tail的存在,LinkedHashMap才形成了循环双向链表.
head是双向链表的入口.
i)使用
Map<String, Integer> accessMap = new LinkedHashMap<>(16,0.75f,true);
accessMap.put("c",100);
accessMap.put("d",200);
accessMap.put("a",500);
accessMap.get("c");
accessMap.put("d",300);
//输出
a 500
c 100
d 300
ii)自定义实现LinkedHashMap
HashMap存储的是Node类型,即kv键值对。
//自定义实现的话,需要保存节点的先后顺序。
LinkedHashMap源码是对HashMap的Node类型进行修改,加入了before,after属性来保存节点之间的先后顺序,next是解决哈希冲突属性
// 自定义实现:
Map<Integer, Node1> cache = new HashMap<Integer, Node1>();
//Node类里面的key为Integer类,Value为Node1类
自定义一个类Node1,作为Value的存储类型,里面包含after,before属性,保存节点先后顺序
将hashtable替代,jdk1.2.效率高。
3)TreeMap
底层是二叉树数据结构,红黑树(平衡二叉排序树)
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
...
}
TreeSet底层:TreeMap<E,Object>
自然排序:TreeMap的所有key必须实现Comparable接口,所有的key都是同一个类的对象
----这样才能对插入的key进行比较
定制排序:创建TreeMap对象传入了一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序不要求Map的key实现Comparable接口。等下面分析到比较方法的时候在分析这两种比较有何不同
4)Map的遍历
Map的遍历方式有两种 一种是entrySet 而另一种是 Keyset
1)Keyset
Map<Student,String> map = new HashMap<Student,String>();
Set<Student> ks = map.KeySet();
Iterator<Student> it = ks.iterator();
While(it.hasNext())
{
Student key = (Student)it.next();
String value = map.get(key);
System.out.println("key"+key.getName()+",value:"+value);
}
//简化上述写法
Map<String,String> map = new HashMap<Student,String>();
for(String key : map.keySet())
{
System.out.println(key+"="+map.get(key));
}
2)entrySet
Iterator<Map.Entry<Student,String>> it = map.entrySet().iterator();
while(it.hasNext())
{
Map.Entry<Student,String> me = it.next();
Student key = (Student)me.getKey();
String value = me.getValue();
System.out.println(key.getName()+".."+value); //key是student的对象 所以可通过getName获取数据