1.集合的理解(好处)
(1)可以动态保存任意多个对象,使用比较方便
(2)提供了一系列方便操作对象的方法:add,remove,set,get等
(3)使用集合添加,删减等等代码,简洁
2.集合的框架体系
1. 集合主要是两组(单列集合 , 双列集合)
2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
3. Map 接口的实现子类 是双列集合,存放的 K-V
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("tom");
HashMap hashMap = new HashMap();
hashMap.put("NO1", "北京");
hashMap.put("NO2", "上海");
3.Collection接口和常用方法
3.1collection接口的概念
1.collection实现的子类中可以存放多个元素,每个元素可以是object
2.有些collection实现类,可以存放重复的元素(list集合),有些不可以(set集合)
3.有些collection实现类,可以有序(list),也可以无序(set)
4.collection接口没有直接实现子类,而是通过子接口set和list来实现
3.2collection常用方法
List list = new ArrayList()
(1)add: 添加单个元素 list.add() //可添加任何对象
(2)remove: 删除指定元素 list.remove(int index) //index下标
(3)contains: 查找元素是否存在
(4)size: 获取元素个数
(5)isEmpty: 判断是否为空
(6)clear:清空 list.clear();
(7)addAll: 添加多个元素 //也可添加某个list集合
(8)containsAll: 查找多个元素是否都存在
4.List接口和常用方法
4.1 List接口基本介绍
4.2 List接口常用方法
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
//1.void add(int index, Object ele):在 index 位置插入 ele 元素
//在 index = 1 的位置插入一个对象
list.add(1, "cc");
System.out.println("list=" + list);
//2.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);
//3.Object get(int index):获取指定 index 位置的元素
//4.int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("tom"));
//5.int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add("cc");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("cc"));
//6.Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
//7.Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换. list.set(1, "玛丽");
System.out.println("list=" + list);
//8.List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);
}
4.3 List的子接口—ArrayList底层结构与源码分析
1.注意事项:
(1)可以加人多个null,因为(list数据可以重复)
(2)底层是用数组来实现数据存储
(3)基本等同于Vector ,但ArrayList线程不安全,因此执行效率高,在多线程情况下,不建议用
2.底层操作机制
ArrayList list = new ArrayList(); //使用的无参构造,那初始的elementData容量为10!!!for(int i= 1;i<=10;i++){ list.add(i); }
1.debug —ArrayList list = new ArrayList();后会出现: elementData —— 一个空数组
2.执行 list.add(i)
(1)这里首先会进行一个装箱操作,将int 转为 Integer 型
(2)并不会马上就要执行element[size++] = e ,(size初始化为0)需要先验证一下该数组是否有空间来添加数据 ,所以就要转去执行 ensureCapacityInternal
minCapacity = size+1 (0+1=1)
3.执行ensureCapacityInternal()
传一个空数组和minCapacity给上面这个函数,让其返回一个minCapacity
先比较该数组是否为空,是的话,就调用Math.max 比较DEFAULT_CAPACITY(10)与minCapacity 返回大的
由于第一次“扩容”,所以返回DEFAULT_CAPACITY(10)
4.执行ensureExplicitCapcity() 上面已经拿到了最小需要的容量
modCount (记录该数组被修改了多少次)
minCapacity-element.length > 0 用当前加入元素了的数组容量((至少容纳已加入元素的空间),比如我已经有一个元素在数组里面,然后我要添加第二个,那说明此时我至少需要两个空间才可以容纳两个数据) - 当前(实际拥有)的数组容量 来判断需不需扩容(grow)真正的扩容操作
小于0 代表当前实际拥有的容量够用,就不需要扩容!!!
5. 执行 grow() 真正扩容操作
oldCapacity+(oldCapacity>>1)扩容机制(向右移动一位相当于变成原来的1/2) 例如12扩容 则 12+12/6=18
然后考虑两种情况:
(1)新扩容的长度-至少需要的数组长度<0 说明我扩容的数组长度不够数据的添加 所以就需要将至少需要的数组长度(minCapacity)作为要扩容的长度 ;若结果为大于,则表明新扩容的长度比至少需要的数组长度(minCapacity)大,就不需修改,直接用新扩容的长度就行。
(2)考虑如果扩容的长度大于 最大的数组长度(很大) 跳过
两种情况都不满足就进入 最后一条语句(数组的复制copyOf)
Arrays.copyOf ( arr,n) 【在arr数组的后面加上n个空间(长度)】,这样不会因为我复制数组而导致前面已有的值被丢弃!!!
做到这步的时候,一步步返回到add的位置
若使用有参构造(则一开始的数组就是8,直接在8的基础上扩容) 其余过程跟无参大致一样
8+4=12
5.Vector底层结果和源码分析
5.1 Vector类概念:
(1)底层也是一个对象数组,与ArraysList大致一样。
(2)是线程同步(线程安全)因为带有synchronized(开发时需要线程同步安全 考虑Vector),但效率不高
5.2 扩容机制
(1)使用无参构造时,默认一开始就是10 ,然后按两倍扩容
1. Vector vector = new Vector()
new Vector() 底层
public Vector() { this(10); } 调用另一个构造器
2.public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
3. //确定是否需要扩容 条件 : minCapacity - elementData.length>0
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
4.如果 需要的数组大小 不够用,就扩容 , 扩容的算法
capacityIncrement:默认是0,可以自己修改数值,来决定要扩容多少
newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); //就是扩容两倍.
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
(2)使用有参时,直接按照给的参数两倍或任意数值扩容
例如:最初数值容量为10,设置了capacityIncrement 为5 【根据自己需求来自定义扩容空间】
这说明,我下一次扩容时,是按照10+5=15来扩容 15+5=20 每次扩容5个空间
6.LinkedList分析
6.1 LinkedList概念
(1)底层使用了双向链表和双端队列的特点
(2)可添加任意元素(可重复)
(3)线程不安全,没有同步
6.2 源码分析
添加(其余的看源码)
1. LinkedList linkedList = new LinkedList();
public LinkedList() {}
2. 这时 linkeList 的属性 first = null last = null
3. 执行 添加 public boolean add(E e) { linkLast(e); return true; }
4.将新的结点,加入到双向链表的最后 void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else l.next = newNode;
size++; modCount++;
}
6.3 ArrayList 和 LinkedList比较
底层结构 | 增删效率 | 改查的效率 | 线程安全 | |
ArrayList | 可变数组 | 较低,数组扩容(1.5倍) | 较高 | 不安全 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 | 不安全 |
Vector | 可变数组 | 较低,数组扩容(2倍) | 较低 | 安全 |
(1)若改查的操作多,选择ArrayList或者 Vector 【和通过索引定位】
(2)增删操作多,选 LinkedList
7. Set接口(常用方法与Collection一样)
没有索引,说明不能使用普通for循环。
7.1 hashSet底层分析 (底层是hashMap)
1. 执行 HashSet()
public HashSet() {
map = new HashMap<>();
}
2. 执行 add() public boolean add(E e) {
return map.put(e, PRESENT)==null; //(static) PRESENT = new Object();
}
3.执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值
算法 h = key.hashCode()) ^ (h >>> 16) 【向左移16位,类似逻辑左移】
public V put(K key, V value) { //key = "" value = PRESENT 共享
return putVal(hash(key), key, value, false, true);
}
4. 执行 putVal
注:Node[] tab; Node p; int n, i; //定义了辅助变量
table 就是 HashMap 的一个数组,类型是 Node[]
if 语句表示如果当前 table 是 null, 或者 大小=0 //就是第一次扩容,到 16 个空间.
(1)根据 key(计算hash),得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置 //并把这个位置的对象,赋给 p
(2)判断 p 是否为 null
(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="???",value=PRESENT)
(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
如果p 不为null ,这说明该索引的tab位置上已经有了一个数据,那就执行else的内容:
//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样【tab所放位置一样】
//并且满足 下面两个条件之一:
//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后 相同就不能加入
就让e=p 跳到 if(e!=null)返回一个对象
8. LinkedHashSet
8.1 LinkedHashSet的说明
(1)是hashSet的子类
(2)底层维护了一个数组+双向链表
(3)添加顺序和输出顺序一样(双向链表的作用)
9. Map接口 (晕晕晕)
9.1 HashMap说明
没有实现同步,线程不安全
当有相同的key 就等价于value替换(修改value)
真正的k-v 是放在HashMap$Node 里面 (内部类) Node实现了Entry接口
Set Collection 指向 HashMap$Node 【方便遍历】把每一组对象做成一个entry
然后把entry放在entrySet 集合 , 该集合存在的元素类型是Entry
key表面放在Set集合的KeySet里面,values表面放在Collection里面
KeySet:获取所有的键(封装到Set集合中)
values封装在Collection
(Set)entrySet:获取所有关系k-v
entrySet中,定义的类型是Map.Entry ,但是实际上存放的还是HashMap$Node(方便遍历)【接口的多态】
存放原因(HashMap$Node 实现了 Map.Entry ) 方便原因:Map.Entry 提供重要方法 getKey()、getValue()
注:为了从HashMap$Node 取出k-v (把HashMap$Node转为Entry 然后Entry放在EntrySet)
先做向下转型
Map.Entry entry = (Map.Entry) obj
values:获取所有的值
9.2 遍历
//第一组: 先取出 所有的 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());
}
例题:
10. Hashtable
10.1 Properties 【继承了Hashtable】
11. Collections 工具类
常见方法:
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
//我们希望按照 字符串的长度大小排序
Collections.sort(list, new Comparator() { 传入一个新定义的接口 (匿名内部类)
@Override
public int compare(Object o1, Object o2) {
//可以加入校验代码. return ((String) o2).length() - ((String) o1).length();
}
});
System.out.println("字符串长度大小排序=" + list);
//Object max(Collection):根据元素的自然顺序(字典顺序),返回给定集合中的最大元素
System.out.println("自然顺序最大元素=" + Collections.max(list));
//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
//比如,我们要返回长度最大的元素
Object maxObject = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("长度最大的元素=" + maxObject);
//void copy(List dest,List src):将 src 中的内容复制到 dest 中
ArrayList dest = new ArrayList();
//为了完成一个完整拷贝,我们需要先给 dest 赋值,大小和 list.size()一样 否则会抛异常
for(int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest, list);
System.out.println("dest=" + des
12. 总结!!!