一、Collection and collections definition
Collection就是java中的集合,又叫容器,可用于储存多个数据。 常见的集合有:List,Queque,Set,和 Map。
java.util.Collection 就是一个接口,其中 List和Set 都直接实现了(implements) Collection接口。
Collections 是一个工具类,为集合提供一系列静态方法。
二、数据结构(5种)
数据结构就是集合存储的方式。 常见的数据结构有 栈 (stack),数组 (Array),链表 (Linked),队列 (Queque) 和红黑树 (Red-black Tree)。
1. 栈 stack
栈的出、入都是同一个口,
先进后出,first in last out
存储元素叫压栈,push
取出元素时叫出栈,也叫弹栈 pop
运算受限的线性表:仅允许在表的一端进行插入和删除操作
2. 队列 Queque
队列的进出口分别在两侧,
先进先出,first in first out
运算受限的线性表:仅允许在表的一端插入,在表的另一端进行删除
(chapter5. Java数据结构与java算法一、2. 队列 中有详细代码)
因为先进先出的特点,不管是增加值到队列还是删除,front和rear都是先++,再增/删。
思路分析
-
队列空:front == rear
加入队列时,需要将尾指针往后移:先判断 front == rear (队列是否为空,是否满↓)-> rear+1 , -
队列满:rear == maxSize -1
无法存入数据。 -
队列未满:rear < maxSize-1
若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear 所指的数组元素中
3. 数组 Array
- 有序可重复的存储元素
- 查询元素快
通过固定的下标可以快速查找元素 - 增删元素慢
数组的长度是固定的,增加元素时会创建新的数组,将指定的元素存储后,将原有的数组复制到新的数组上。(删除则是不复制被删的元素。)
4. 链表 linked list
通过连接节点node
链表结构包括:单向链表和双向链表。
单链表:
- 查询慢,增删快。
- 链表中只有一条链子,不能保证元素的顺序(存、取的顺序不一致)
双链表:
- 查询快,增删快。
- 链表中有两条链子,有一条专门记录元素的顺序,是一个有序的集合。
(chapter5. Java数据结构与java算法一、3.链表 中有详细代码)
5. 红黑树
二叉树 binary tree:每个节点不超过2 的有序树。
平衡树:左右孩子都相等
不平衡树:左右孩子都不相等
查找/排序树:二叉树的基础上,元素是有序的。
红黑树:本身是二叉树
- 根节点是黑的
- 每个红节点的子节点是黑的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
三、Collection
关系图:
collection<T> 单列集合
List<T> Queque<T> Set<T>
ArrayList<T> Vector<T>(遗留) LinkedList<T> ? HashSet<T> TreeSet<T>
LinkedHashSet<T>
Map<K,V>双列集合
HashMap<K,V> treeMap<K,V>
LinkHashMap<K,V>
HashTable<K,V>(遗留)
ConcurrentMap<K,V>
synchronizedMap<K,V>
Collection
Collection是父接口,定义了单列集合的通用方法,常用方法:
public boolean add(E e):把传入的对象添加到集合中。返回值为boolean,一般返回true,接不接收都可以。
public boolean remove(E e):把传入的对象从集合中删除。返回值为boolean,成功删除返回true,删除失败返回false。
public void clear():清空集合中所有元素。
public boolean isEmpty():判断当前集合是否为空。
public boolean contains(E e):判断当前集合是否包含传入的参数。
public int size():返回集合中元素的个数。
public object[] toArray():把集合中的元素存储到数组中。
Iterator<E> iterator() :返回在此collection的元素上进行迭代的迭代器
集合的迭代方式:
- for和for-each的目标只能是Collection或数组
- Iterator
Iterator 迭代器
java.util.Iterator 接口 将集合元素进行迭代访问/遍历
Iterator 是一个接口,无法直接使用,需要Iterator 接口的实现类对象(也可以用匿名类)。
常用方法:
public E next() :返回迭代的下一个元素
public boolean hasNext():查询是否有下一个元素,有返回true。
ps:在迭代时,集合中没有元素时还继续取出会抛出异常:没有集合元素(java.util.NoSuchElementException)所以使用next() 前需要用hasNext() 判断
//e.g.1 toArray & for-each
object[] arr = collection.toArray();
for(object a:arr)
{
System.out.print(a);
}
//e.g.2 toArray & for
object[] arr1 = collection.toArray();
for(int i = 0; i<arr1.length; i++)
{
System.out.println(arr1[i]);
}
//e.g.3 Iterator
//step:1 用iterator()接口接收,2.hasnext()判断 3.next()取出元素
Collection<String> student = new Collection<>();
Iterator<String> it = student.iterator();
while(it.hasNext)
{
String s = it.next();
System.out.println(s);
}
for(Iterator<String> it2 = student.iterator(); it2.hasNext();)
{
String s = it2.next();
System.out.println(s);
}
1. List
java.util.List implements Collection
特点:元素带有索引、有序、可重复
有索引,可以进行普通for循环遍历
常用方法:
1 继承Collection的所有方法
2 特有方法:
public void add(int index, E element):增加指定索引(位置)的元素
public E get(int index):得到指定索引的元素
public E remove(int index):移除指定索引的元素
public E set(int index, E element):替换指定索引的元素,返回更新前的元素。
public E asList():数组转化为List集合
boolean contains(xx):查询是否包含某元素,返回boolean
迭代方式:
- ListIterator(只能遍历list)
接口特有功能:增加元素,替换元素,获取前一个和后一个元素。 - Iterator
- for
- for-each
1.1 ArrayList
java.util.ArrayList
数组存储类型
特点:增删慢,查询快,多线程,长度不固定。
局限:比一般的数组的速度慢一些;
集合转数组:Object[ ] ob = list.toArray();
数组转集合:for(Object a : ob) { list.add(a); }
复制:clone()
ArrayList<String> (转str[]) 转char[](翻转内容:nohtyP fo neZ ehT->The Zen of Python)方法一:
ArrayList<String> (转str[]) 转char[](删除重复内容: TThhee ZZeenn ooff PPyytthhoonn ->The Zen of Python)方法二:
public static void main(String[] args) {
ArrayList<String> A = new ArrayList<>();
A.add("Apple");
A.add("Avocado");
A.add("Blueberries");
A.add("Durian");
A.add("Lychee");
ArrayList<String> B = new ArrayList<>();
B = (ArrayList<String>) A.clone();
}
我的题解:提取翻转的、不重复的整数。
int转数组toCharArray()、boolean contains(xx)、数组转集合for(Object a : ob) { list.add(a); }
https://blog.nowcoder.net/n/206aa365a450466caab996e09559fb0c
我的题解:不重复的字符个数统计
string转数组toCharArray()、boolean contains(xx)
https://blog.nowcoder.net/n/b1ee4fb6c0604809923a0bc509e86f9b
1.2 Vector (遗留)
数组存储类型,
特点:单线程
JDK1.2 后被ArrayList取代
1.3 LinkedList
java.util.LinkedList
链表存储类型(双链表)
特点:增删快,查询慢,链表实现
(ArrayList相比,LinkedList的增删操作效率更高,而查改操作效率较低。)
常用方法:
1 继承Collection和List的所有方法
2 特有方法(专门操作表头和表尾元素):
public E addFirst(E e):添加元素在列表的表头
public E addLast(E e):添加元素在列表的表尾
public E getFirst():返回列表的第一个元素
public E getLast():返回列表的最后一个元素
public E removeFirst():移除并返回列表第一个元素
public E removeLast():移除并返回列表最后一个元素
public E pop():从此列表的堆栈顶弹出一个元素??
public void push(E e):从此列表的堆栈顶推入一个元素??
public boolean isEmpty():查询列表是否包含某元素,不包含返回true。
2. Queque
队列存储类型
常用方法
pull():从队列取出一个元素,若不存在会返回null
remove(): 返回被移除元素,若不存在会抛出异常
3. Set
java.util.Set implements Collection
Set 对Collection接口方法没有扩充,只是更为严格了。
特点:元素无序,不可重复。
常用方法:
Set 集合取出元素的方式:Iterator,for-each (没有索引不可用for循环)
3.1 HashSet
java.util.HashSet implements Set
底层的实现是 java.util.HashMap支持
HashSet是根据对象的哈希值来确定元素在集合中的位置。
储存原理:
哈希表= 数组+链表+红黑树(JDK1.8后新增)
哈希表存放的是哈希值(hashCode):根据哈希值找到集合中元素的存储位置。元素进行分组,元素不同但相同哈希码的是一组,并链在一起。(哈希值是系统随机给出的模拟地址)
特点:无序,存储快,查找非常快
构造方法:
HashSet()
构造一个新的空集合; 背景HashMap实例具有默认初始容量(16)和负载因子(0.75)。
HashSet(Collection<? extends E> c)
构造一个包含指定集合中的元素的新集合。
HashSet(int initialCapacity)
构造一个新的空集合; 背景HashMap实例具有指定的初始容量和默认负载因子(0.75)。
HashSet(int initialCapacity, float loadFactor)
构造一个新的空集合; 背景HashMap实例具有指定的初始容量和指定的负载因子。
常用方法:
- 继承collection的方法:add(), size(), remove(Object obj), isEmpty(), contains(Object obj)。
object的方法:public native int hashCode():返回哈希值(native代表该方法调用的是本地操作系统的方法)
保证元素唯一:复写hashcode() 和equals()
reference:https://blog.csdn.net/xiaoyaosheng19/article/details/84783520
我的题解 - LeetCode52:两个链表的第一个公共节点(利用hashcode的特点查找公共结点)
https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/solution/liang-ge-lian-biao-de-di-yi-ge-gong-gong-yb4d/
//判断唯一性???
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
@override
public int hashcode()
ruturn object.hash(name,age);
@override
public boolean equals(object o1,object o2)
{
if(o1 == o2)
{
return true;
}
if(o1 == null || ????????)
}
3.1.1 LinkedHashSet
java.util.LinkHashSet extends HashSet + implements LinkedHashMap
操作与hashSet相同,方法继承LinkedHashMap
特点:
底层是LinkedHashMap,即哈希表(数组+链表+红黑树)+链表:多了一条链表用于记录存储顺序,保证元素有序
常用方法:
四个构造方法?
(hashSet 和LinkedHashSet的区别:无序和有序)
3.2 TreeSet
TreeSet(二叉树) 有序
特点:
- TreeSet() 使用二叉树的原理对add()的对象按照指定的顺序排序(升、降序)
- String和 Integer对象都可以进行默认的TreeSet排序,但自定义类 (Person,Student) 需要实现comparable接口并override compareTo() 或传入自定义的comparator。
- 比较此对象(this) 与指定对象的顺序时,如果该对象(this) 小于,等于或大于指定对象,则分别返回负整数,零或正整数。
4.Map<K,V>
Java.util.Map 接口,储存这种一一对应的关系(映射)
特点:双列集合
一个元素包含两个值(key 和 value)
key不可重复且只对应一个value,value可重复
常用方法:
public V put(K key, V value):添加指定key和value 到Map集合去
public V remove(Object key):移除指定key(和value)。key存在,返回被remove的值(Integer装),不存在返回null。
**clear():**移除全部key
map的value转为collection 然后移除???↓:通过value移除key
public V get(Object key):通过指定key获取value。返回被get的值(Integer装),不存在返回null。
boolean containsKey(Object key) :查询是否包含指定key。包含返回true
public Set<K> keySet():获取Map集合中的所有key,存储到Set<>集合中。(常用于遍历)
public Set<Map.Entry<K,V>> entrySet():获取到Map集合中所有key,value对象的集合(Set<>集合)(常用于遍历)
System.out.println(maps);
Collection<String> values = maps.values();
if(true == values.contains("The Lab")) {
values.remove("The Lab");
}
System.out.println(maps);
Entry键值对对象
将键值对的对应关系封装成对象,常用方法:
public K getKey():获取Entry对象中的Key
public V getValue():获取Entry对象中的Value
public Set<Map.entry<K,V>> entrySet():获取到Map<,>集合中所有的键值对对象的集合(Set集合)
Map集合遍历键找值
- 获取Map的key,返回Set集合存储所有key:keySet()
- 遍历Set集合,得到每一个Key:for-each,Iterator
- 获取key所对应的值:get(K key)
map转为数组:
Object[] keys = map.keySet().toArray();
Object[] values = map.values().toArray();
//keySet遍历
Set<Car> set = hm.keySet();
for(Car c: set)
{
Integer value = hm.get(c);
sop(c.getName() + value);
}
//匿名.keySet()
HashMap<String, String> hm = new HashMap<>();
for(String k: hm.keySet())
{
sop(k + ": " + hm.get(value));
}
Map集合遍历键值对
- 获取Map的entry对象,返回Set集合:entrySet()
- 遍历Set集合,得到每一个entry对象:for-each,Iterator
- 通过entry对象获取entry对象的键与值:getKey(),getValue()
//entrySet遍历
Set<Map.Entry<Car,Integer>> es =hm.entrySet();
for(Map.Entry<Car,Integer> e:es)
{
Car key = entry.getKey();
Integer value = entry.getValue();
sop(key.getName() + value);
}
4.1 HashMap<K,V>
java.util.HashMap<K,V> implements Map<K,V>
底层是哈希表
存储数据结构:数组+链表+红黑树
Java1.8之后,为了提升在 hash 冲突严重时(链表过长)的查找性能,新增了红黑树。
时间复杂度取决于链表的长度:O(n),链表元素超过8个就会将链表自动转为红黑树,查找时降低时间复杂度为:O(logN)
HashMap 的默认初始容量:默认初始容量是16
HashMap 的容量限制:HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。
特点:
- 无序,不重复,单向链表
- 查询快,多线程,线程不安全(不同步)
- 只允许一个Key值为空,允许value为空
构造方法4个:
常见方法:
containsKey()
containsValue()
Map.getOrDefault(key, defaultValue): 如果存在该key,则返回该key对应的value,否则返回defaultValue。
The getOrDefault(Object key, V defaultValue) method of Map interface, implemented by HashMap class is used to get the value mapped with specified key. If no value is mapped with the provided key then the default value is returned.
map转为数组:
Object[] keys = map.keySet().toArray();
Object[] values = map.values().toArray();
使HashMap满足线程安全可以使用:
ConcurrentMap<K,V>
synchronizedMap<K,V>
hashmap扩容是需要重新哈希吗
不需要重新hash。java8在实现HashMap时做了一系列的优化,其中一个重要的优化即在扩容的时候,原有数组里的数据迁移到新数组里不需要重新hash,而是采用一种巧妙的方法:https://blog.csdn.net/zlp1992/article/details/104376309
HashMap 的插入流程
Reference:https://blog.csdn.net/v123411739/article/details/106324537
面试题: https://blog.csdn.net/qq_39207350/article/details/114253140
我的题解 - 多组去重随机数:
https://blog.nowcoder.net/n/c9f4986ef4b742a6acb889e75e81ed99
4.2 TreeMap<K,V>
TreeMap implements SortedMap
可排序,默认按键值的升降序
使用排序的映射建议用TreeMap,
使用时,key必需实现comparable接口并override compareTo() 或在构造TreeMap时传入第二个参数:自定义的comparator接口。
我的题解 - 合并表记录(牛客):
treeMap自动排序
https://blog.nowcoder.net/n/8ca95ce4f0e04e5185dc762bd815d88d
4.3 LinkHashMap<K,V>
LinkHashMap extends HashMap<K,V>
底层是哈希表+链表
有序,不重复的集合,查询快(put(Key,Value) 和 get(Key))
(保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。)
我的题解 - LeetCode 50:
https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/solution/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-by-oxil5/
https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/solution/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-by-inmzt/
LinkedHashMap可以很好的支持LRU算法(Least recently used)
LinkedListMap与LRU小结
使用LinkedHashMap实现LRU的必要前提是将accessOrder标志位设为true以便开启按访问顺序排序的模式。我们可以看到,无论是put方法还是get方法,都会导致目标Entry成为最近访问的Entry,因此就把该Entry加入到了双向链表的末尾:get方法通过调用recordAccess方法来实现;
put方法在覆盖已有key的情况下,也是通过调用recordAccess方法来实现,在插入新的Entry时,则是通过createEntry中的addBefore方法来实现。这样,我们便把最近使用的Entry放入到了双向链表的后面。多次操作后,双向链表前面的Entry便是最近没有使用的,这样当节点个数满的时候,删除最前面的Entry(head后面的那个Entry)即可,因为它就是最近最少使用的Entry。
总结
本文从linkedhashmap的数据结构,以及源码分析,到最后的LRU缓存实现,比较深入地剖析了linkedhashmap的底层原理。
总结以下几点:
1 linkedhashmap在hashmap的数组加链表结构的基础上,将所有节点连成了一个双向链表。
2 当主动传入的accessOrder参数为false时, 使用put方法时,新加入元素不会被加入双向链表,get方法使用时也不会把元素放到双向链表尾部。
3 当主动传入的accessOrder参数为true时,使用put方法新加入的元素,如果遇到了哈希冲突,并且对key值相同的元素进行了替换,就会被放在双向链表的尾部,当元素超过上限且removeEldestEntry方法返回true时,直接删除最早元素以便新元素插入。如果没有冲突直接放入,同样加入到链表尾部。使用get方法时会把get到的元素放入双向链表尾部。
4 linkedhashmap的扩容比hashmap来的方便,因为hashmap需要将原来的每个链表的元素分别在新数组进行反向插入链化,而linkedhashmap的元素都连在一个链表上,可以直接迭代然后插入。
5 linkedhashmap的removeEldestEntry方法默认返回false,要实现LRU很重要的一点就是集合满时要将最久未访问的元素删除,在linkedhashmap中这个元素就是头指针指向的元素。实现LRU可以直接实现继承linkedhashmap并重写removeEldestEntry方法来设置缓存大小。jdk中实现了LRUCache也可以直接使用。
LinkedHashMap支持LRU算法的Reference:https://blog.csdn.net/a724888/article/details/80290276
4.4 HashTable<K,V>(遗留)
java.util.HashTable<K,V> extends Dictionary类
底层是哈希表
单线程,线程安全(同步)
不可存在null key, null value
JDK1.2后被HashMap取代。使用线程不安全的可以用HashMap,需要线程安全可以用ConcurrentMap(并发性更好)。
但是其子类properties仍在使用(唯一一个和IO流结合的集合)e.g. 连接数据库时会用到properties。
ps:
Java 9,Map,List,Set 的静态工厂方法of() 可以更方便的创建集合的不可变实例:
- Map.of(“b”, 1, “c”, 2);
- set.of(“a”, “b”, “c”);
- List.of(“a”, “a”, “b”);
1. of() 只是Map,List,Set的静态方法。
2. 返回的集合是不可变的,即个数确定之后不可重复,否在会抛出异常:
UnsupportedOperationException
3. Set,Map接口在调用方法时,不能有重复的元素,否则会抛出异常:illegalArguementException
5. 存储和增删速度
LinkedList 和ArrayList
LinkedList增删快于ArrayList, ArrayList查找?速度快。
HashSet增删快,查找元素快;TreeSet和LinkedHashSet增删速度慢。
HashMap查询快,TreeMap增删慢,LinkedHashMap查询快,增删快,但是效率低于HashMap。
四、Collections
java.utils.Collections 是一个集合工具类,为集合提供一些静态方法。
常用方法
public static <> boolean addAll(Collection <> c, T … elements ):往c集合中添加多个元素T…。
public static <> void shuffle(List<?> list):打乱顺序
public static <> void sort(List<?> list):按默认规则排序(ASCE: o1-o2; this -o /DCE)
前提:
- 被使用的集合里储存的元素必须实现comparable接口
- 重写compareTo()
public static <> void sort(List<?> list, comparator<? Super T>):按指定规则排序。
- comparator:不需要实现对象类,可以直接使用sort时override排序规则