java集合又是一个新世界了,从前在我刚接触java的时候,一直在纠结 集合这东西到底有啥用,后来代码写的多了。才开始学习集合
集合也叫容器。顾名思意 就是存放对象的器皿。
主要是由两大接口派生而来
:一个是 Collecton接口,主要用于存放单一元素;对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue。
另一个是 Map 接口,主要用于存放键值对。
它们四个的基本区别;javaguide里写的很好
List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。
Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
**
List
**
list接口下定义了操作对象的常用方法 像我们平时经常用的添加add 获取get 删除remove 等等 如果需要遍历那么在Collecton下提供了一个Iterator, 它有两个主要方法 在遍历中经常用:
hasnext 这是一个boolean 就是判断是否存在另一个可访问的元素。(经常做循环遍历的判断条件)
next()完整的含义应该是getnext 就是获取下一个对象。你可以把它丢尽while循环中用hasnext做判断 遍历出所有list的值。
···
public class ListTest {
public static void main(String[] args) {
List linkedlist = new LinkedList();
List arraylist = new ArrayList();
for (int i=0;i<10;i++) {
Integer integer =i;
linkedlist.add(integer);
arraylist.add(integer);
}
//两种方式遍历linkedlist arraylist同理
Iterator linkedlsitIterator = linkedlist.iterator();
while (linkedlsitIterator.hasNext()){
System.out.println(linkedlsitIterator.next());
}
for (Integer integer:linkedlist){
System.out.println(integer);
}}}
···
最常用的list实现就是:Arraylist 与 LinkedList 它们的内部方法很相似 但是底层实现不同-进而导致-性能不同
##List底层数据结构:
Arraylist 底层使用的是 Object 数组;
LinkedList 底层使用的是 双向链表 数据结构
(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
A查询快 插入删除慢:
这是底层的数组结构 你可以理解为A的元素都是有序紧挨着的(形象的说就是: 当我寻找第n个元素的时候,我直接可以算出第n个元素在哪里)所以查的快。 插入/删除慢是因为 这个元素没了/加了。 它后面的所有元素位置都要改变!
L插入删除快 查询慢:
底层是双向链表结构,接触过数据结构的朋友应该之后的,双向链表里面的节点并不是有序紧紧挨着的,它们互相之间能够串联起来是因为每个节点存着 上一个节点和下一个节点的引用。 这就意味着查询的话你就只能一个一个找了 把所有遍历一遍。这就很慢。 而增加你只需要把节点断开 然后和新节点重新“连接”一下,不会影响到其他元素。
–关于list集合元素的排序 通常可以之间调用sort方法,如果你要自定义排序那么看重写比较器comparator接口:
可以看我的这篇博客:(举例实践 自定义Comparator 的用法)
线程安全性:
注意 这俩都不是线程安全的。
Set
set是无序的 (注意这里的无序不代表随机) /不可重复的。
比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同:
HashSet 是 Set 接口的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值;
LinkedHashSet 是 HashSet 的子类,能够按照添加的顺序遍历;
TreeSet 底层使用红黑树,元素是有序的,排序的方式有自然排序和定制排序,不能存null值。
queue
Queue 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 先进先出(FIFO) 规则。
比较重要的是优先队列: PriorityQueue:
PriorityQueue 是在 JDK1.5 中被引入的, 其与 Queue 的区别在于元素出队顺序是与优先级相关的,即总是优先级最高的元素先出队。
PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。
PriorityQueue 是非线程安全的,且不支持存储 NULL 和 non-comparable 的对象。
PriorityQueue 默认是小顶堆,但可以接收一个 Comparator 作为构造参数,从而来自定义元素优先级的先后。
就是说它始终保证最优先的那个元素在堆的顶部。其他的元素不是严格排序的。通过这个特效我们可以做很多事,比如循环不停的取出顶端的值,就可以实现排序功能, 或者可以方便的获取第K个优先级元素。
Map
接下来就是重头戏了 - Hashmap
这个集合 你会到处看见 很重要 很复杂 面试必问巴拉巴拉的。看到这些东西正常的新手都会有一层排斥情绪。
你要告诉自己 这东西是个工具! 不是论文!我个人的经验还是先实践后理论,不用太死磕它的底层实现,
基本用法–动手实践项目中多用用–大概看看底层实现和内部原理–再实践–再回来仔细研究它的理论–再实践。
额外:与它相关的HashTable已经被淘汰了 忘记它吧。
##线程安全:
HashMap 是非线程安全的。
##对 Null key 和 Null value 的支持:
HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;
##初始容量大小和每次扩充容量大小:
HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。 基本的数学描述就是它的扩容每次都是:使用 2 的幂作为哈希表的大小。 为什么这样做?
Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ (n - 1) & hash
我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。
##底层结构:数组+链表+红黑树
同一个链表的hash值是相同的,当链表的长度>8时, 链表会转换为红黑树结构 这样查询更方便。
遍历
他的遍历方式很多 我知道的就有7种:
(不用死记硬背,需要的时候查一下就好了)
个人经常用的比如:
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "JDK");
map.put(3, "Spring Framework");
map.put(4, "MyBatis framework");
map.put(5, "Java中文社群");
// 遍历
Iterator<Integer> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key);
System.out.println(map.get(key));
}
}
}
···
public class HashMapTest {
public static void main(String[] args) {
// 创建并赋值 HashMap
Map<Integer, String> map = new HashMap();
map.put(1, “Java”);
map.put(2, “JDK”);
map.put(3, “Spring Framework”);
map.put(4, “MyBatis framework”);
map.put(5, “Java中文社群”);
// 遍历
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
}
···