java集合框架图
看图可知,主要分为两类:Collection 和 Map,Collection
主要用于存储一组对象,Map
用于存储键-值对。
对这二者再细分
Collection接口:
Map接口
集合框架总结
一、Collection 接口的接口 对象的集合(单列集合)
- List 接口:元素按进入先后有序保存,有序,可重复
- LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
- ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
- Vector 接口实现类 数组, 同步, 线程安全
- Set 接口: 仅接收一次,无序,不可重复,并做内部排序
- HashSet 使用hash表(数组)存储元素
- LinkedHashSet 链表维护元素的插入次序
- TreeSet 底层实现为二叉树,元素排好序
- HashSet 使用hash表(数组)存储元素
二、Map 接口 键值对的集合 (双列集合)
- Hashtable 接口实现类, 同步, 线程安全
- HashMap 接口实现类 ,没有同步, 线程不安全
- LinkedHashMap 有序
- TreeMap 红黑树对所有的key进行排序
一、Collection接口
//因为Collection是接口,不能实例化,所以我们用他的一个实现类ArrayList来演示
List list = new ArrayList<>();
list.add("mk");
list.add(true);
list.add(12.3);
也可以存储指定类型的数据
List<String> list = new ArrayList<>();
list.add("mk");
常用方法
int size(); | 获取集合大小 |
boolean isEmpty(); | 判断是否为空 |
boolean contains(Object var1); | 判断是否包含 |
Object[] toArray(); | 将集合转换为数组 |
boolean add(E var1); | 添加元素 |
boolean addAll(Collection var1); | 添加一个集合的全部元素 |
boolean remove(Object var1); | 删除集合中某个元素 |
boolean remove(int index); | 删除集合中指定下标的元素 |
boolean removeAll(Collection var1); | 删除一个集合的全部元素 |
void clear(); | 清空集合 |
boolean containsAll(Collection var1); | 判断是否包含另一个集合的元素 |
boolean equals(Object var1); | 判断是否相同 |
default Spliterator spliterator(); | 得到该集合的分离器 |
default Stream stream(); | 转换为Stream流 |
default Stream parallelStream(); | 转换为Stream并行流 |
测试
public class Test {
public static void main(String[] args) {
//定义一个集合并向上转型
Collection<String> obj = new ArrayList<>();
//1.int size(); 获取集合大小
int size = obj.size();
//2.boolean isEmpty(); 判断是否为空
boolean empty = obj.isEmpty();
//3.boolean contains(Object var1); 判断是否包含
boolean contains = obj.contains(null);
//4.Object[] toArray(); 将集合转换为数组
Object[] objects = obj.toArray();
//5.boolean add(E var1); 向集合增加元素
boolean result = obj.add("Hello World");
//6.boolean remove(Object var1); 向集合移除元素
boolean remove = obj.remove(null);
//7.boolean containsAll(Collection<?> var1); 判断是否包含另一个集合的元素
boolean containsResult = obj.containsAll(new ArrayList<>());
//8.boolean addAll(Collection<? extends E> var1); 添加一个集合的全部元素
boolean addAllResult = obj.addAll(new ArrayList<>());
//9.boolean removeAll(Collection<?> var1); 删除一个集合的全部元素
boolean removeAllResult = obj.removeAll(new ArrayList<>());
//10.void clear(); 清空集合
obj.clear();
//11.boolean equals(Object var1); 判断是否相同
boolean equalsResult = obj.equals(null);
//12.default Spliterator<E> spliterator(); 得到该集合的分离器
Spliterator<String> spliterator = obj.spliterator();
//13.default Stream<E> stream(); 转换为Stream流
Stream<String> stream = obj.stream();
//14.default Stream<E> parallelStream(); 转换为Stream并行流
Stream<String> stringStream = obj.parallelStream();
}
}
遍历方法
两种遍历方法:
- Iterator迭代器
- 增强for循环(foreach循环)
方法1:Iterator迭代器
//1. 先得到collection对应的迭代器
Iterator iterator = collection.iterator();
//2. 开始遍历
//判断是否还有下一个元素
while (iterator.hasNext()) {
//返回下一个元素
Object next = iterator.next();
System.out.println(next);
}
//重置iterator
iterator = collection.iterator();
作用
相当于一个指针,用于遍历一个集合
1.为什么需要迭代器?
在实际应用中经常需要将容器遍历判断满足业务需求。
2.如何实现迭代?
Iterator类中有 hasNext()和next() 两个方法,因为遍历过程首先得判断是否为空,不为空才能继续遍历。
Iterator类具有remove()方法,因为Collection类中的removeIf()方法是jdk1.8后才有的,所以需要满足能有通过逻辑删除的需求。
3.如果希望再次使用一个迭代器,需要重置
因为使用完一次迭代器之后,指针会指向最后一个元素,我们想再次使用,就得将其重置,否则会报错
4.生成代码的快捷键
itit
示例
public class Test {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add(new Book("三国演义","罗贯中",10.1));
collection.add(new Book("小李飞刀","古龙",5.1));
collection.add(new Book("海贼王","尾田",34.6));
//1. 先得到collection对应的迭代器
Iterator iterator = collection.iterator();
//2. 开始遍历
//判断是否还有下一个元素
while (iterator.hasNext()) {
//返回下一个元素
Object next = iterator.next();
System.out.println(next);
}
//重置iterator
iterator = collection.iterator();
}
}
class Book {
String name;
String author;
double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
方法2:foreach循环
for (Object object : collection) {
System.out.println(object);
}
增强for循环,就是foreach循环,可以代替iterator迭代器
特点:增强for就是简化版的iterator,本质一样,它的底层也是 Iterator。数组和集合都能用。
快捷键:
collection.for
示例
public class Test {
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add(new Book("三国演义","罗贯中",10.1));
collection.add(new Book("小李飞刀","古龙",5.1));
collection.add(new Book("海贼王","尾田",34.6));
for (Object object : collection) {
System.out.println(object);
}
}
}
class Book {
String name;
String author;
double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
Collection集合适用场景分析
一.1、List接口
Collection接口的方法,List接口和Set接口都有,但是List接口和Set接口他们自己的方法,对方是没有的。
特点
- List集合类中元素有序(即添加顺序和取出顺序一致)
- 可重复
- List集合中的每个元素嘟有其对应的顺序索引,即支持索引
也就是可以 用 list.get(3) 来拿到第4个元素
常用方法
除去Collection接口的方法,List接口还有自己独特的方法
因为List接口是有序的,所以它可以用很多带有索引的方法
package edu.tjdz.javaSE.collection;
import java.util.*;
/*
测试List接口中的常用方法
1、List集合存储元素特点:有序可重复
有序:List集合中元素由下标。
从0开始,以1递增。
可重复:存储一个1,还可以在存储一个1.
2、List即是Collection接口中的子接口,那么肯定List接口有自己“特色”的方法:
以下只列出List特有的常用的方法
void add(int index, E element) 在列表的指定位置添加元素(第一个参数是下标)
Object get(int index) 根据下标获取元素(元素的下标,从0开始,以1递增)
int indexOf(Object o) //获取指定对象第一次出现的索引
int lastIndexOf(Object o) //获取指定对象最后一次出现的索引
Object remove(int index) //删除指定下标位置的元素
Object set(int index, Object element) //修改指定位置元素(元素下标,修改的值)
*/
public class CollectionTest07 {
public static void main(String[] args) {
//创建List集合
//List myList = new LinkedList();
//List myList = new Vector();
List myList = new ArrayList();
//添加元素
myList.add("A"); //默认都是向集合尾部添加元素
myList.add("B");
myList.add("C");
myList.add("D");
myList.add("A");
//在列表的指定位置添加元素(第一个参数是下标)
//这个方法使用不多,因为对于ArrayList集合来说,效率较低(因为ArrayLIst底层是数组,添加的时候会涉及到元素位移的问题)
myList.add(1,"KING");
//迭代
Iterator it = myList.iterator();
while(it.hasNext()){
Object elt = it.next();
System.out.println(elt);
}
System.out.println("------------------------------");
//根据下标
Object firstobj = myList.get(0);
System.out.println(firstobj); //A
//因为有下标,所以List集合有自己特殊的遍历方式
//根据下标遍历【List集合特有的方式,Set没有。】
for(int i=0;i<myList.size();i++){
Object obj = myList.get(i);
System.out.println(obj);
}
//获取指定对象第一次出现的索引
System.out.println(myList.indexOf("KING")); //1
//获取指定独享最后一次出现处的索引
System.out.println(myList.lastIndexOf("A")); //5
//删除指定下标位置的元素
myList.remove(0);
System.out.println(myList.size()); //5
System.out.println("================================");
//修改指定位置元素
myList.set(0,"Soft");
//索引方式遍历集合
for(int i=0;i<myList.size();i++){
Object obj = myList.get(i);
System.out.println(obj);
}
}
}
三种遍历方法
List可以用collection接口的两种方法:Iterator迭代器、foreach循环
还能有一种自己独特的方法,因为List接口是有序的,所以可以用索引的方式来遍历
//遍历集合
for(int i=0;i<myList.size();i++){
Object obj = myList.get(i);
System.out.println(obj);
}
ArrayList、Vector、LinkedList区别
底层 | 查询 | 增删 | 线程安全 | 效率 | 扩容倍数 | |
ArrayList | 数组 | 快 | 慢 | 不安全 | 高 | 如果有参构造,默认大小:参数,满后按照1.5倍扩容 如果无参构造,默认大小:0,第一次容量扩大到10,从第二次开始1.5倍扩容 |
Vector | 数组 | 快 | 慢 | 安全 | 低 | 如果有参构造,默认大小:参数,满后每次扩大两倍 如果无参构造,默认大小:10,满后就按照2倍扩容 |
LinkedList | 链表 | 慢 | 快 | 不安全 | 高 | 链表,不用扩容 |
ArrayList
扩容机制
- ArrayList中维护了一个Object类型的数组elementData.
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
- 扩容方法有两种,add 和 addAll方法
2和3 分别就是下图中的两个
注意:代码中的右移就是÷2的意思
Vector
(1)Vector可以实现可增长的对象数组。与数组一样,可以使用整数索引进行访问的组件。不过,Vector的大小是可以增加或者减小的,以便适应创建Vector后进行添加或者删除操作。
(2)同时Vector是线程安全的!底层使用的是synchronized进行加锁。
扩容机制
如果有参构造,默认大小:参数,满后每次扩大两倍
如果无参构造,默认大小:10,满后就按照2倍扩容
LinkedList
底层机制
- LinkedList底层维护了一个双向链表.
- LinkedList中维护了两个属性first和last分别指向首节点和尾节
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
一.2、Set接口
特点
- 无序(添加和取出的顺序不一致)
- 不允许重复元素,所以最多包含一个null
- 没有索引,也就是不能用索引来取出数据
常用方法
除去Collection接口的方法,Set接口也有自己独特的方法
两种遍历方法
也就是Collection接口的两种遍历方法:Iterator迭代器、foreach循环
因为Set是无序的,所以不能使用索引的方式来遍历
面试题:Set不能添加重复的元素?
public class SetTest {
public static void main(String[] args) {
Set set = new HashSet();
// Hashset不能添加相同的元素/数据?
set.add("lucy");//ok
set.add( "lucy");//no
set.add(new Dog("tom" ));//OK
set.add(new Dog( "tom"));//0k
//再加深一下。非常经典的面试题。
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//no
System.out.println( "set=" + set);
}
}
class Dog{
String name;
Dog(String name){
this.name = name;
}
}
输出
set=[hsp, 集合.Dog@4554617c, 集合.Dog@1b6d3586, lucy]
lucy那里,因为地址一样,都是常量池中的,所以是重复元素,所以第二个不能加入。
Dog那里,因为两个dog 都是新的对象,地址不一样,所以不算重复的元素,所以可以加入。
那第三对 是为什么?
HashSet
Java中哈希集(HashSet)概念,实现以及操作_java hashset_Sueko的博客-CSDN博客
- HashSet底层是HashMap, HashMap底层是(数组+链表+红黑树)
- HashSet是基于HashMap来实现的,实现了Set接口,同时还实现了序列化和可克隆化。而集合(Set)是不允许重复值的。
- 所以HashSet是一个没有重复元素的集合,但不保证集合的迭代顺序,所以随着时间元素的顺序可能会改变。
- 由于HashSet是基于HashMap来实现的,所以允许空值,不是线程安全的
LinkedHashSet
- LinkedHashSet 是 HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
- LinkedHashSet 不允许添重复元素
二、Map接口
特点
注意:这里讲的是JDK8的Map接口特点
- Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
- Map 中的 key和 value可以是任何引用类型的数据,会封装到HashMap$Node·对象中
- Map 中的key 不允许重复、Map 中的value 可以重复
- Map 的key可以为null, value也可以为null,注意key 为null, 只能有一个,value 为null ,可以多个.
- 常用String类作为Map的key
- key和 value之间存在单向一对一关系,即通过指定的key 总能找到对应的value
- 一个k-v 就是一个 Entry
常用方法
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
常用实现类
Map 接口 键值对的集合 (双列集合)
- Hashtable 接口实现类, 同步, 线程安全
- HashMap 接口实现类 ,没有同步, 线程不安全
- TreeMap 红黑树对所有的key进行排序
Map接口的常用实现类:HashMap(子类LinkedHashMap)、TreeMap和Hashtable(子类Properties).
遍历方法
第一种:用map.KeySet()方法拿到key
先用map.KeySet()方法拿到所有的key,将这些key存到一个Set集合中,再遍历这些key,用map.get(key)方法拿到value
可以用Set集合遍历的foreach循环遍历key,也可以用Set集合的迭代器key
注意:不推荐这种方法,因为只能获取key,要想获取对应的value,需要重复计算,效率低
public static void main(String[] args) {
Map map = new HashMap();
map.put("张三","李四");
map.put("路飞","女帝");
map.put("鸣人","雏田");
map.put("佐助","小樱");
map.put("邓超","孙俪");
System.out.println("第一种:用for循环--------------");
for(Object key : map.keySet()){
System.out.println(key + "-" + map.get(key));
}
System.out.println("第二种:用迭代器--------------");
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
}
第二种:用map.values方法取出所有的value
用map.values() 方法拿到所有的value,存到一个collection中,在用collection接口的两种遍历方法去遍历
注意:这种方法只能取出value,不能取出key,不推荐
public static void main(String[] args) {
Map map = new HashMap();
map.put("张三","李四");
map.put("路飞","女帝");
map.put("鸣人","雏田");
map.put("佐助","小樱");
map.put("邓超","孙俪");
Collection values = map.values();
System.out.println("第一种:用for循环--------------");
for(Object value : values){
System.out.println(value);
}
System.out.println("第二种:用迭代器--------------");
Iterator iterator = values.iterator();
while (iterator.hasNext()) {
Object value = iterator.next();
System.out.println(value);
}
}
第三种:用map.entrySet()方法取出k-y
首先通过map.entrySet()方法,可以获取到一个Set集合,这个集合中的每一个元素就是Map中的一个键值对。然后通过循环遍历这个Set集合,可以依次取出每对的键和值。该方法使用了foreach循环,代码简洁明了,且能获取Map的键和值,是最常见且多数情况最可取的遍历方式。
注意:推荐使用这种方式,效率高
public static void main(String[] args) {
Map map = new HashMap();
map.put("张三","李四");
map.put("路飞","女帝");
map.put("鸣人","雏田");
map.put("佐助","小樱");
map.put("邓超","孙俪");
Set entrySet = map.entrySet();
System.out.println("第一种:用for循环--------------");
for(Object entry : entrySet){
//将entry 转换成 Map.Entry
Map.Entry m =(Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
System.out.println("第二种:用迭代器--------------");
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Object entry = iterator.next();
//将entry 转换成 Map.Entry
Map.Entry m =(Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}