Java集合框架提供了一套性能优良、使用方便的接口和类,它们位于java.util包中
下面的图可较为简单的说明集合之间的关系 . 左面的collection接口存储的方式类似于数组,存储的是单一元素 , 而map接口存储的是键值对(key-value).
一.Collection
•Collection 接口存储一组不唯一,无序的对象
常见方法:
- add(Obj) 增加一个元素 addAll(Coll) 增加一个集合
- clear() 清空集合 remove(obj) 移除一个元素 retainAll(coll)求2个集合的交集
- size() 集合的长度 isEmpty() 集合是否为空
- contains(obj) 集合是否包含某个元素 containsAll(coll)集合是否包含另一个个元素
collection的迭代方式: (没有索引,所以不能使用for循环)
-
加强for循环
for(Object obj:coll){ System.out.println(obj); }
-
java.util.Iterator 专门负责对Collection集合进行迭代的迭代器 java.lang.Iterable: 实现这个接口允许对象成为 "foreach" 语句的目标
hasNext() 查看是否存在下一个元素
next(); 移动指针 且获取当前指针指向的元素
/**
* Iterator<E> iterator()
返回在此 collection 的元素上进行迭代的迭代器。
*/
//创建集合对象
Collection coll = new ArrayList();
Iterator it = coll.iterator();//实现类对象
while(it.hasNext()){//查看是否存在下一个元素
Object obj = it.next();//指针向下移动一个 获取指针当前指向的元素
System.out.println(obj);
}
此迭代器是可以使用 for 循环的, 所以也可以写成另一种方式(第二种方式Iterator在for循环内, 生命周期短, 占用内存小)
for(Iterator it= coll.iterator();it.hasNext();){
System.out.println(it.next());
}
二.List
•List 接口存储一组不唯一,有序(索引顺序)的对象
•底层存储方式是数组。
List的实现类
•1.ArrayList实现了长度可变的数组,在内存中分配连续的空间。
•优点:遍历元素和随机访问元素的效率比较高
•缺点:添加和删除需要大量移动元素效率低,按照内容查询效率低
•2.LinkedList采用链表存储方式, 在内存中随机分配的空间 , 前一个元素中一部分存放值 ,另一部分存储着下一个元素的索引位置。
•优点:插入、删除元素时效率比较高
•缺点:遍历和随机访问元素效率低下
这两种list接口的优缺点我们可以从底层实现结构来解释:
ArrayList 获取元素:索引获取, 0×1101+(索引×元素宽度)=元素地址 ,可以直接定位到元素地址, 所以快
添加、删除元素:需要拷贝数组+移动元素+数组拷贝 , 效率低
LinkedKList 获取元素:获取下一个元素,先找到上一个元素,根据其中存放的下一个位置的索引可以找到下个元素,所以效率低
添加、删除元素:只需要改变存储位置就可以了, 效率低
常见方法: 除了collection的方法 , 还多了一些针对于索引的操作 索引位置从0开始
sublist(int fromIndex , int toIndex) 截取集合 , 左闭右开
- 增加 add(idnex,obj) add(index,coll) 可添加元素或者集合
- 删除 remove(index)
- 修改 set(index,obj)
- 查看 get(index) indexOf(obj)查看obj出现的索引位置 lastIndexOf()查看最后一次出现的位置
//注意, 修改元素 返回的是要修改的元素值
//1、创建list集合
List ls = new ArrayList();
//2、添加元素
ls.add("长生剑");
ls.add(0, "孔雀翎");
System.out.println(ls); //打印的结果是[孔雀翎,长生剑]
System.out.println(ls.set(0, "情人箭"));
//返回的是要修改的元素值---->孔雀翎,而非修改之后的值-->情人箭
System.out.println(ls); // [情人箭,长生剑]
LinkedList 多了一些对于表头和表位的操作:
- 查看头元素 peek() peekFirst() 如果集合数据为空 返回null
- 查看头元素 getFirst() element() 如果集合数据为null 获取时会报错 java.util.NoSuchElementException
- 查看尾元素 getLast() peekLast()
- 添加头元素 addFirst() offerFirst()
- 添加尾元素 addLast() offer() offerLast()
- 删除头元素 removeFirst() pollFirst() poll()
- 删除尾元素 removeLast() pollLast()
- 常用的遍历方式有三种:相比collection多了可以使用索引的for循环
-
List ls = new ArrayList(); //===========普通for========== for(int i = 0;i<ls.size();i++){ System.out.println(ls.get(i)); } //===========增强for========== for(Object obj:ls){ System.out.println(obj); } //===========迭代器=========== Iterator it = ls.iterator(); while(it.hasNext()){ System.out.println(it.next()); }
此外,对迭代器的补充:
比如说, 我们有一个需求:遍历集合对象 遍历过程中查看是否存在java 如果存在 添加元素 javascript
采用for循环遍历是完全OK的
for(int i = 0;i<ls.size();i++){
if("java".equals(ls.get(i))){
ls.add("javascript");
}
}
但当使用foreach和Iterator迭代器循环时,就都会报错
Exception in thread "main" java.util.ConcurrentModificationException
当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常
while(lt.hasNext()){
Object obj = lt.next();
String str = (String)obj;
if("java".equals(str)){
lt.add("javascript");
}
}
为什么呢?
因为 : ls 对象 指向了 list对象的地址 而 it 对象 指向了list对象地址, 一个通过指针迭代元素 一个要通过指针遍历元素 造成了一个共享资源被多个对象争抢, 这种情况就跟我们在修改文件的同时去修改文件名, 系统禁止我们操作是一样的. 此时如果出现该情况list集合的处理办法是对外抛出并发修改异常
解决方法是 : 通过ListIterator解决该问题 , 可以保证 并发修改 并且还可以做逆向遍历
ListIterator lt = ls.listIterator();
while(lt.hasNext()){
Object obj = lt.next();
String str = (String)obj;
if("java".equals(str)){
lt.add("javascript");
}
}
至于正向迭代和逆向迭代,下面的代码可以对比一下:
//正向遍历(默认的迭代方向)
ListIterator lt = ls.listIterator();
while(lt.hasNext()){
System.out.println(lt.next());
}
//逆向遍历
while(lt.hasPrevious()){
System.out.println(lt.previous());
}
注意:当我们在使用List独有的迭代器ListIterator中的previous()方法时,不能在最开始就逆向遍历。我们在遍历一个集合的时候,相当于有一个指针,最开始是指着集合的最前端,如果我们最开始就逆向遍历,虽然不会报错,但是会输出空值。
- 3.Vector
上述的两种集合实现类都可以vector来替换,vector是同步的,是线程安全的,但是运行太慢了,如果想用一个线程安全且效率比vector高的有很多,所以基本上不用vector.
三.Set
•Set接口存储一组唯一,无序的对象
•(存入和取出的顺序不一定一致)
•HashSet:采用Hashtable哈希表(数组+链表, 1.8之后是数组+红黑树)存储结构
HashSet的底层是由一个HashMap实例支持的.
线程不安全
•优点:添加速度快,查询速度快,删除速度快
•缺点:无序
针对hashset无序的缺点 , 在此基础上又有了LinkedHashSet
• 采用哈希表存储结构,同时使用链表维护次序
•有序(添加顺序)
Java 1.7的hash表的底层实现方式:
1.通过求当前存储对象的hash值
2.通过hash算法获取到对应的元素位置
3.如果该位置上没有元素, 直接填充到该位置
4.如果该位置上有元素, 在该位置上形成一个链表, 将需要添加的元素追加到元素后(两个元素互不相等)
5.如果两个元素相等, 则会调用equals方法, 比较相同位置上的元素, 如果不相等, 重复第四步, 追加元素, 如果相等, 不追加
HashSet存储自定义对象
如果通过HashSet存储自定义对象时 想要通过属性的值确定当前对象是一个对象 , 那么需要重写hashcode 方法计算位置,并且重写equals 比较内容.
•TrssSet(基于红黑树的平衡树)
TrssSet的底层是由一个TreeMap实例支持的.
•优点:有序(排序后的升序) 查询速度比List快(按照内容查询)
•缺点:查询速度没有HashSet快
常用方法: 多了一些关于顺序的方法 , 添加时将数据进行排序 默认升序 唯一
- 获取最大值中的最小值[等于] ceiling()
- 获取最大值中的最小值 higher()
- 获取最小值中的最大值[等于] floor()
- 获取最小值中的最大值 lower()
- 删除第一个元素 pollFirst() 删除时先获取在删除
- 删除最后一个元素 pollLast()
TreeSet的两种比较器
1.内部比较器
TreeSet存储自定义对象, 自定义对象必须实现java.lang.Comparable 接口 重写compareTo方法 [正整数 大----0 相等----负整数 小], 否则会报异常.
java.lang.ClassCastException:
com.java1001.collection.Car cannot be cast to java.lang.Comparable 类型转换异常
//自定义对象实现java.lang.Comparable接口
class Car implements Comparable{
private String color;
private String type;
//重写compareTo方法
@Override
public int compareTo(Object o) {
Car c = (Car)o;
return this.color.compareTo(c.color); //按照color升序排序
}
}
2.外部比较器
创建TreeSet集合时 指定比较器 java.util.Comparator接口 编写内部类 或者是其他方式完成该类的实现类对象 . 在创建对象时 将实现类对象传入TreeSet集合中. 然后往集合中添加数据,数据的排序方式按照 指定的外部比较器进行排序.
//创建时,指定一个实现了comparator接口的实现类对象
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Car c1 = (Car)o1;
Car c2 = (Car)o2;
return c1.getColor().length()-c2.getColor().length(); //按照color的字符串长度升序排序
}
});
四.Map
•特点key-value映射
它有2个子类 , HashMap 和 TreeMap
常用方法:
- 创建map集合 Map<String,Integer> mps = new HashMap<>();
- 添加 put() 如果添加了重复键 值会覆盖 而且添加时会返回 前一次键对应的值
- 查看 size() isEmpty() get(key) containsKey() 是否包含key containsValue() 是否包含value
- 删除 remove(key) 删除时 返回对应键 的值 删除
迭代方法:
1.通过map集合提供的ketSet 方法返回key的set集合 , 再通过get(key) 方法获取到value
Set<String> keys = mps.keySet();
for(String key:keys){
System.out.print(key+"-->"+mps.get(key)+",");
}
System.out.println();
2.通过map集合提供的entrySet 方法 返回set集合 这个集合中存放了Entry对象 在Entry中对象中, 维护了 map集合中的一个k-v.
遍历set集合获取每一个Entry对象 , 通过对象提供的getKey , getVlaue 获取当前对象维护的键值映射
Set <Map.Entry<String, String>> entrys = map.entrySet()
Set<Entry<String, Integer>> entrys = mps.entrySet();
for(Entry<String,Integer> entry:entrys){
System.out.println(entry.getKey()+"-->"+entry.getValue());
}
3.只遍历键或者值
//=================所有key==================
Set<String> keys = mps.keySet();
for(String key:keys){
System.out.print(key+",");
}
//=================所有value==================
Collection<Integer> values = mps.values();
Iterator<Integer> it = values.iterator();
while(it.hasNext()){
System.out.print(it.next()+",");
}
•HashMap
•Key无序 唯一(Set)
•Value无序 不唯一(Collection)
HashMap 和 HashTable 的区别:
- HashMap不同步, 线程不安全 , 运行效率高点 而HashTable同步, 线程安全 ,效率低
- HashMap允许使用null 作为键值 ,而HashTable不可以
若想HashMap同步 , 最好在创建时完成这一操作,以防止对映射进行意外的非同步访问
Map m = Collections.synchronizedMap(new HashMap(...));
除了上述区别外, 其他的使用基本相同 . 底层存储结构是hash表 按照key值 进行位置判定以及内容比较
容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。默认的初识容量是16 . 默认的加载因子是0.75 .如果加载因子过高 , 虽然会节省空间 , 但相应的会使查询成本增加 . 如果过低 , 会经常重复rehash 操作 , 降低性能 .
HashMap存储自定义对象要重写hashcode以及equals方法
重要的子类有: LinkedHashMap , 它是有序的HashMap 速度快
•TreeMap
•有序 速度没有hash快
•底层存储结构是 二叉树 默认有序 存储数据是会自动排序 按照key 键值
TreeMap<Integer,String> mps = new TreeMap<>();
常用方法:
迭代方法:
- firstEntry() firstKey() 第一个(键最小的)
- lastEntry() lastKey() 最后一个(键最大的)
- ceilingEntry() ceilingKey() 大值中求小值 包含等于
- higherEntry() higherKey() 大值中求小值 不包含等于
- floorEntry(22) floorKey(22) 小值中求大值 包含等于
//用Iterator迭代器
Set<Map.Entry<Integer,String>> sets= mps.entrySet();
for(Iterator<Map.Entry<Integer, String>> it= sets.iterator();it.hasNext();){
Map.Entry<Integer, String> entry = it.next();
System.out.println(entry.getKey()+"-->"+entry.getValue());
}
TreeMap存储自定义对象作为键:
com.java1001.map.Goods cannot be cast to java.lang.Comparable
TreeMap 中的键作为自定义对象时要注意 :
要么对象实现内部比较器 实现java.lang.Comparable 接口 重写compareTo方法
要么创建TreeMap对象时 指定了外部比较器 指定比较器 java.util.Comparator接口 编写内部类 或者是其他方式完成该类的实现类对象
•问题:Set与Map有关系吗?
•采用了相同的数据结构,用于map的key存储数据上是Set