目录
· 8.3 ArrayList 与 LinkedList 的比较
1. 集合是什么
Java集合类是Java将一些基本的和使用频率极高的基础类进行封装和增强后再以一个类的形式提供。集合类是可以往里面保存多个对象的类,存放的是对象,不同的集合类有不同的功能和特点,适合不同的场合,用以解决一些实际问题。
em...容器,动态保存任意多个对象
2. 为什么需要集合(集合的优点)
Java语言里已经有一种方法可以存储对象,那就是数组。数组不仅可以存放基本数据类型也可以容纳属于同一种类型的对象。数组的操作是高效率的,但也有缺点。比如数组的长度是不可以变的,数组只能存放同一种类型的对象(或者说对象的引用),使用数组进行增加/删除元素麻烦。
so,集合 :1.可以动态保存任意多个对象,方便;2.提供了一系列方便的操作对象的方法:add、remove、set、get等一堆;3.使用集合添加,删除元素,简单。。。
3. 集合的框架体系(重要)
1 . 集合主要是两组(单列集合 , 双列集合) 2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合 3. Map 接口的实现子类 是双列集合,存放的 K-V对
4. Collection接口
Collection 接口没有直接的实现子类,通过子接口Set和List来实现
4.1 常用方法:
// 说明:以 ArrayList 实现类来演示
List list = new ArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);
// remove:删除指定元素
//list.remove(0);//删除第一个元素
list.remove(true);//指定删除某个元素
System.out.println("list=" + list);
// contains:查找元素是否存在
System.out.println(list.contains("jack"));//T
// size:获取元素个数
System.out.println(list.size());//2
// isEmpty:判断是否为空
System.out.println(list.isEmpty());//F
// clear:清空
list.clear();
System.out.println("list=" + list);
// addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//T
// removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);//[聊斋]
4.2 Collection接口遍历元素 :
1.Iterator迭代器
用于遍历Collection集合中的元素
所有实现了Collecrion接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator忌口的对象,即返回一个迭代器。
//得到一个集合的迭代器
Iterator iterator = collection.iterator();
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//将下移后的的集合位置上的元素返回
System.out.println(iterator.next());
}
注意:1. Iterator仅用于遍历集合,iterator本身并不存放对象
2. 调用iterator.next()方法之前必须调用iterator.hasNext()进行检测,如不调用,且吓一跳记录无效,直接调用iterator.next()会抛出NoSuchElementException异常
3. 当退出while循环后,这是迭代器指向最后的元素,如果希望再次遍历,需要重置迭代器
2. 增强for循环遍历
for(元素类型 元素名 : 集合名){
访问元素
}
5. List接口
5.1 介绍
List 接口时Collection接口的子接口,List集合类中元素有序(添加顺序与取出顺序一致)、且可重复,2. 支持索引(每个元素都有对应的顺序索引)
5.2 常用方法
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
// void add(int index, Object ele):在 index 位置插入 ele 元素
//在 index = 1 的位置插入一个对象
list.add(1, "lcy");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定 index 位置的元素
//说过
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("tom"));//2
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add("lcy");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("lcy"));
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换. list.set(1, "玛丽");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist)
5.3 List 三种遍历方式
方式一:使用iterator
方式二:使用增强for
方式三:普通for循环
public static void main(String[] args) {
//List 接口的实现子类 Vector LinkedList
//List list = new ArrayList();
//List list = new Vector();
List list = new LinkedList();
list.add("jack");
list.add("tom");
list.add("鱼香肉丝");
list.add("北京烤鸭子");
//遍历
//1. 迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
System.out.println("=====增强 for=====");
//2. 增强 for
for (Object o : list) {
System.out.println("o=" + o);
}
System.out.println("=====普通 for====");
//3. 使用普通 for
for (int i = 0; i < list.size(); i++) {
System.out.println("对象=" + list.get(i));
}
}
6. ArrayList
ArrayList 中维护了一个Object类型的数组elementDate用来存储数据,容量就是这个数组缓冲区的长度,ArrayList可以添加null,而且可添加多个。(ArrayList是线程不安全的,执行效率高)
注意:
1. 当创建ArrayList对象时,如果使用的时无参构造器, elementData容量为0,第一次添加时,扩容elementDate为10(private static final int DEFAULT_CAPACITY = 10),如果需要再次扩容,则扩elementDate的1.5倍。
2. 如果使用的是指定大小的构造器,则elementDate容量为指定大小,若需扩容,怎扩容elementDate的1.5倍。
使用和方法略。。。
7. Vector
Vector底层也是一个对象数组,protected Object[] elementData;
Vector 是线程同步的(线程安全,操作方法带有synchronized),如果需要线程同步安全,可以考虑使用。
使用与ArrayList差不多,差别是Vector 是 1. 线程安全但效率不高; 2. 需要扩容的时候按照2倍扩容。
8. LinkedList
8.1 介绍
说明:LinkedList 底层实现了双向链表和双端队列的特点,可以添加任意元素而且可以重复,没有实现同步(线程不安全)。
LinkedList 底层维护了两个属性 first 和 last,分别指向首节点和尾节点
每一个节点(Node对象)里面又维护了三个属性:prev 、item 、next,其中通过 prev 指向前一个节点,item 是存放真正的数据(即添加到LinedList中的数据),next 指向下一个节点,因此实现双向链表。如果需要进行添加和删除,那么仅需要把两边的节点里的 prev 和 last 改变指向即可,所以效率较高。
主要是因为Node里面有两个属性prev和last(类型为Node),所以可以指向Node对象。
8.2 使用
LinkedList linkedList = new LinkedList();
//添加
linkedList.add("巴巴托斯");
linkedList.add("摩拉克斯");
linkedList.add("派蒙");
//删除
linkedList.remove("派蒙");
System.out.println(linkedList);
//修改 set
linkedList.set(1,"巴尔");
System.out.println(linkedList);
//查找
System.out.println(linkedList.get(0));
//在首部/尾部添加
linkedList.addFirst("天理");
linkedList.addLast("派蒙");
System.out.println(linkedList);
// 方法很多,indexOf,clear,size,contains等等
· 8.3 ArrayList 与 LinkedList 的比较
9. Set 接口
9.1 Set接口说明和常用方法
实现了 Set 接口的显现类:添加和去除顺序不一致,即无序的,而且没有索引,可以添加null,但是只能添加一个,因为不允许重复元素。
因为和 List 接口一样,Set接口也是Collection的子接口,所以常用方法和 Collection 接口一样。
//接口不能实例,所以以 Set 接口的实现类 HashSet 来举例
Set set = new HashSet();
//添加
set.add("屑狐狸");
set.add("做饭影");
set.add("飞机蒙");
System.out.println(set);
//删除
set.remove("飞机蒙");
//获取集合长度
System.out.println(set.size());
//contains 判断元素是否存在集合中,返回boolean
System.out.println(set.contains("大亨钟离"));
//等等
9.2 Set 接口的遍历方式
1. 使用迭代器 2. 增强 for 循环 (不能使用索引的方式来获取)
//迭代器遍历
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//增强for
for (Object o : set
) {
System.out.println(o);
}
10. HashSet
10.1 HashSet说明
1. HashSet 底层实际上是HashMap(HashMap底层是:数组 + 链表 + 红黑树)
2. 可以存放null,但是只能存放一个
3. 不保证存储顺序和取出顺序一致
10.2 添加机制
分析
1. HashSet 的添加,底层是 HashMap 的 put()方法
由于Map 是 K-V 对 ,所以 K 是添加的值,而 PRESENT 是 一个公共的(static),之后的每次添加 K 实际上是真正添加的数据,V是所有都共享的,都一样。
2. 把添加的数据经过hash(数据的hashcode经过hash得到hash值,hash值经过运算得到一个索引值),得到需添加的数据应存放的位置。
3. 下面就是真正存放的操作
//执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i; //定义了辅助变量
//table 就是 HashMap 的一个数组,类型是 Node[]
//if 语句表示如果当前 table 是 null, 或者 大小=0
//就是第一次扩容,到 16 个空间. if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据 key,得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置
//并把这个位置的对象,赋给 p
//(2)判断 p 是否为 null
//(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K, V> e;
K k; //
//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
//并且满足 下面两个条件之一:
//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树, //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {//如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点
// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先 table 扩容. // 只有上面条件不成立时,才进行转成红黑树
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//size 就是我们每加入一个结点 Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
10.3 HashSet 扩容机制
11. LinkedHashSet
11.1 说明
11.2 底层机制
12. Map 接口
12.1 说明
1. Map 和 Collection 并列存在,Map 用于保存具有映射关系的数据 Key-Value(一一对应),其中,k和v可以是任意引用类型的数据(String类常用作Key的类型),会封装到HashMap$Node对象中
2. Map 中的key是不允许重复的(当Map集合有相同key的时候,key是不可以重复的。后面的key对应的值value会覆盖前面key对应的值value),原因在 HashSet 中分析底层时一样。但是V是可以重复的
3. K 和 V 都可以为空,但是 K 只能有一个 null ,而 V 可以多个
4. 一对 K-V 是存放在一个HashMap$Node 中, 因为Node 实现了 Entry 接口
12.2 Map 接口常用方法
Map map = new HashMap();
map.put("雷神影","lcy");//OK
map.put("屑狐狸","lcy");//OK
map.put("咕噜咕噜滚下山真君","lcy");//OK
map.put("富婆天权","lcy");//OK
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("屑狐狸");
System.out.println("val=" + val);
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty());//F
// clear:清除 k-v
//map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("hsp"));
12.3 遍历方式
Map map = new HashMap();
map.put("雷神影","lcy");//OK
map.put("屑狐狸","lcy");//OK
map.put("咕噜咕噜滚下山真君","lcy");//OK
map.put("富婆天权","lcy");//OK
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();
//(1) 增强 for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
13. HashMap
说明:
HashMap 是 Map 接口实现类使用频率最高的,与 HashSet 一样, 不保证映射的顺序,底层是 数组 + 链表 + 红黑树;没有实现同步,线程不安全的。
扩容机制和 HashSet 相同
14. HashTable
HashMap vs HashTable:
15. Properties
使用:
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
//增加
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
//通过 k 获取对应值
System.out.println(properties.get("lic"));//88
//删除
properties.remove("lic");
System.out.println("properties=" + properties);
//修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);
16. TreeMap
相比HashMap来说,TreeMap多继承了一个接口NavigableMap,也就是这个接口,决定了TreeMap与HashMap的不同:HashMap的key是无序的,TreeMap的key是有序的。
重点是自定义排序规则
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照传入的 k(String) 的大小进行排序
// return ((String) o2).compareTo((String) o1);
//按照 K(String) 的长度大小排序
return ((String) o2).length() - ((String) o1).length();
}
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("lcy","旅程雨");//加入不了
System.out.println("treemap=" + treeMap);