集合继承结构图
Collection集合删除元素的注意事项
使用迭代器之后,再添加元素,会出异常
因为当集合结构发生改变时,迭代器必须重新获取,如果还是用之前的迭代器,会出现异常
在循环体删除元素之后,集合的结构发生了变化,应该重新去获取迭代器
但是下次循环并没有重新获取迭代器,所以会出现异常
出现异常的根本原因:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了,导致迭代器的快照和集合状态不同)
使用迭代器去删除,会自动更新迭代器,并且更新集合(删除集合中的元素)
迭代器删除的一定是迭代器指向的当前元素
重点:在迭代集合元素的过程中,不能调用集合对象的remove方法删除元素。
List的常用方法
void add(int index,Object element) 增
Object set(int index, Object element) 改动
Object get(int index) 获取
int indexOf(Object obj) 查找第一次出现的元素下标
int lastIndexOf(Object obj) 查找最后一次出现的元素下标
Object remove(int index) 删
ArrayList集合
默认初始化容量10(底层先创建了一个长度为0的数组,当添加一个元素的时候,初始化容量10)
集合的底层是一个Object数组
构造方法:
new ArrayList()
new ArrayList(int initialCapacity) initialCapacity是指定数组容量
new ArrayList(Collection<? extends E> c)
ArrayList集合的扩容
增长到原容量的1.5倍
ArrayList集合底层是数组,怎么优化?老办法!尽可能减少扩容,初始化一个合适的容量
数组优点
检索效率高(每个元素占用空间大小相同,内存地址又是连续的,知道首元素的地址,又知道下标,通过数学表达式计算出元素的内存地址所以检索效率最高)
数组缺点
随机增删元素效率较低
无法储存大数据量(内存里很难找到一块非常巨大的连续的内存空间)
面试官常问问题
这么多集合中,你使用哪个集合最多?
- ArrayList集合,因为一般往数组末尾添加元素,效率不受影响,另外,我们检索某个元素的效率高
ArrayList 数组可以添加 null 元素
LinkList集合
链表的优点
由于链表上的元素在空间存储内存地址不连续
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议采用LinkList
链表的缺点
不能通过数学表达式计算被查找元素的内存地址。每一次查找都是从头结点开始遍历,直到找到为止,所以LinkList集合检索查找的效率较低
ArrayList:把检索发挥到极致
LinkList:把随机增删发挥到极致
加元素一般都是在末尾加,所以ArrayList用的比LinkList多
可以添加 null
vector集合
所带的方法都是线程安全的
扩容是扩容2倍,初始容量是10(用无参构造器创建,默认容量是10)
似乎可以根据 " capacityIncrement " 这个变量(扩容增量)自定义扩容,因为底层扩容算法是:
newCapacity = oldCapacity + ( (capacityIncrement > 0) ? capacityIncrement : oldCapacity ) ;
但是一般做多线程开发,不用 Vector ,用 JUC 包中的集合,因为 Vector 中的方法例如:add,remove 等方法不是原子操作,Vector 是相对线程安全,而不是绝对线程安全
HashSet集合
Set 集合,无下标。不能用普通 for 循环依靠下标来遍历
无序,不可重复
无序的底层是依靠哈希算法来排序,无序是指添加和取出的顺序不一致。但每次的运行结果都一样,是固定的
TreeSet集合
Collections 集合工具类
//要想把别的集合(比如ArrayList)转成线程安全,可以用java工具
//java.util.Collections包的功能,具体用法:
List a = ArrayList();
a.add(100);
a.add(10);
a.add(200);
a.add(150);
//1、把List转换成线程安全
Collections.synchronizeList(a);
//2、把List集合排序
//值得注意的是,使用集合工具类排序
//集合元素是自定义类的话,要实现Comparable接口然后重写compareTo方法
Collections.sort(a);
//Set集合是无序的,那怎么排序呢?
Set<String > set = new HashSet();
set.add("9");
set.add("2");
set.add("3");
set.add("1");
set.add("5");
//将Set集合转换成List集合
List<String> list = new ArrayList(set); //这个是ArrayList的第三个构造方法
Collections.sort(list);
for (String s : list) {
System.out.println(s);
}
泛型
JDK5.0之后推出的新特性
泛型这种语法机制只在编译阶段起作用
泛型的好处是什么?
第一:集合中存储的元素统一了
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的向下转型
泛型的缺点是什么?
导致集合中存储的元素缺乏多样性
- 大多数业务中,集合中的元素类型还是统一的。所以这种泛型特性被大家所认可
List<Animal> myList = new ArrayList<Animal>();
Cat c = new Cat();
Bird b = new Bird();
myList.add(c);//添加元素
myList.add(b);
Iterator<Animal> it = myList.iterator();
while(it.hasNext()){
//如果这里不采用泛型,iterator会返回一个Object类型,Object在调用子类特有方法时要进行强转。要instanceof
Animal a = it.next();
a.move();
//但这里Animal调用子类特有方法【cat.catchMouse()】时依然还是要向下转型(强转)
}
增强for(foreach)
这个是JDK5之后的新特性
//用法是可以直接输出数组,但就不能指定长度了,也没有下标
//for(数据类型 变量名:数组){ }
for(String s:arr){
System.out.println(s);
}
增强 for 的底层其实就是一个迭代器。输入联想快捷键:I(大写的 i )
Map的常用方法
返回值 方法
-------------------------------------------------------
V put(K key, v value) 向Map中添加键值对
V get(Object key) 通过key获得value
void clear(); 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个Key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素是否为0
V remove(object key) 通过key删除键值对
int size() 获取Map集合键值对个数
Collection<v> values() 获取Map集合中所有的value,返回一个Collection
Set<k> keySet() 获取Map集合所有的key(所有的键是个set集合)
//注意一下这个方法↓↓↓
Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合
假设有集合 Map叫做 map
方法是这样子用:Set set = map.entrySet();
也就是用一个Set集合去接收,类似创建构造器,通过调用方法去创建。然后这个这个Set集合里面就有Map集合里面的所有key和value元素了
Map.Entry<K,V> 【 .Entry其实是Map里面的一个内部类,支持泛型<K,V>罢了】
HashMap
HashMap的数组上的单链表,数量若达到8(TREEIFY_THRESHODE:树形临界点),而且数组已经扩容到64,则自动变成二叉树(红黑树),当红黑树上的元素减少达到6时,会重新变成单链表(UNTREEIFY_THRESHODE)。扩容是0.75 f ,比如16的0.75是12,要达到13才会扩容
JDK 8的新特性
一般情况下,数组同一个下标的单链表的元素,哈希值都相同。
哈希碰撞:(上面结论的例外情况)同一个数组下标下的链表,元素的哈希值可能不同。也就是哈希值相同,下标一定相同,但反过来就不行
在存、取过程中,先去调用hashCode( ) ,再去调用equals( )
HashMap的扩容是满 0.75 f 自动用位运算扩容
newCap = oldCap << 1 ; 二进制左移一位,也就是乘以 2
K值不能重复,但是V值可以重复
HashMap 和 Hashtable 的区别
HashMap的<K , V>值可以传入null ,可以map.get(null) 求value值
- Hashtable<K , V>值不能传入null ,底层源码很明显,会抛出空指针异常,因为K在调用hashCode时,<K , V>不能为空 (null
初始化容量为11, 0.75 f ,扩容也不同,是 : 新 = (老 << 1) + 1
不过HashMap 和 Hashtable 的底层都是哈希表数据结构,Hashtable的方法都带有synchronized,但对线程处理效率较低,所以使用少。
Properties
Properties的<K , V>值都是String
掌握setProperties() 和 getProperties()
其他方法都是有关 IO 流的,后面讲。
TreeSet 和 TreeMap
遍历集合
Collection的遍历
方法一:用迭代器while循环遍历
方法二:用迭代器普通for遍历(用collection的size( ) )后面加判断就行
方法三:增强for
================
Collection的子类:List 可以用for循环特有的get( )
for (int i = 0; i < list.size(); i++){ System.out.println(list.get(i)); }Collection的子类:Set,没有get( ) 方法,就只能用Collection的那三种,其他后面再学
Map的遍历
方法一:用keySet遍历
方法二:用entrySet遍历
若只需要Value值的话,可以用map.values( ) 去获取所有的value值,保存在Collection中,遍历Collection即可
//创建Map集合并添加元素 Map<Integer,String> map = new HashMap<>(); map.put(1,"xiaoming1"); map.put(2,"xiaoming2"); map.put(3,"xiaoming3"); map.put(4,"xiaoming4"); //遍历Map集合 Method 1 Set<Integer> set = map.keySet();//Set里面存的都是Key for (Integer key : set){ System.out.println(key +"="+map.get(key) ); } //遍历Map集合 Method 2 Set<Map.Entry<Integer,String>> set1 = map.entrySet(); for (Map.Entry<Integer,String> node:set1){ System.out.println(node.getKey()+"--->"+node.getValue()); }