常见集合框架
集合框架
- 集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中也非常常用
- 为了满足不同的业务场景,Java提供了很多不同特点的集合给我们选择
Collection
- 所有单列集合的祖宗,每个元素只包含一个值
常用方法:
-
使用:
import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; public class CollectionTest2API { public static void main(String[] args) { Collection<String> c = new ArrayList<>();//多态 //1.public boolean add(E e) 添加元素,成功返回true c.add("java1"); c.add("java2"); c.add("java1"); c.add("java2"); c.add("java3"); System.out.println(c); //2.public void clear() 清空集合元素 // c.clear(); // System.out.println(c); //3.public boolean isEmpty() 判断集合是否为空 System.out.println(c.isEmpty()); //4.public int size() 获取集合大小 System.out.println(c.size()); //5.public boolean contains(Object obj) 判断集合中是否包含某个元素 System.out.println(c.contains("java")); System.out.println(c.contains("java1")); //6.public boolean remove(E e) 删除某个元素:如果有多个重复元素,则默认删除最前面的一个 System.out.println(c.remove("java1")); System.out.println(c); //7.public Object[] toArray() 把集合转换成数组 Object[] objects = c.toArray(); System.out.println(Arrays.toString(objects)); String[] s = c.toArray(new String[c.size()]); System.out.println(Arrays.toString(s)); System.out.println("----------------------------------"); //把一个集合的全部数据拷贝一份复制到另一个集合中去 Collection<String> c1 = new ArrayList<>(); c1.add("java1"); c1.add("java2"); Collection<String> c2 = new ArrayList<>(); c2.add("java1"); c2.add("java2"); c1.addAll(c2);//把c2集合的全部数据拷贝一份到c1集合中 System.out.println(c1); System.out.println(c2); } }
Collection的遍历方式:
-
迭代器
-
迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator
-
-
增强for
-
格式:
for(元素的数据类型 变量名 : 数组或者集合){ } Collection<String> c = new ArrayList<>(); for(String s:c){ System.out.println(s); }
-
增强for可以用来遍历集合或者数组
-
增强for遍历集合,其本质就是迭代器遍历集合的简化写法
-
-
Lambda表达式遍历集合
-
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合
-
格式:
Collection<String> list = new ArrayList<>(); list.forEach(new Consumer<String>{ @Override public void accept(String s){ System.out.println(s); } }); list.forEach( s -> System.out.println(s)); list.forEach(System.out::println);
-
*ps:*Collection不能普通for循环遍历,因为没有规定索引,但是其子接口list规定了索引,故可以使用普通for循环根据索引遍历
集合存储对象的原理:
*ps:*集合是泛型的,只能存储引用数据类型的对象,故实际存储的都是元素对象的地址
不同单列集合的应用场景
List
特点:
- 添加的元素是有序、可重复、有索引
ArrayList & LinkedList不同:
- 底层实现不同,优缺点不同,适合的场景不同
List集合的特有方法:
-
使用:
import java.util.ArrayList; import java.util.List; public class ListTest1 { public static void main(String[] args) { //1.创建一个ArrayList集合对象(有序 可重复 有索引) List<String> list = new ArrayList<>(); list.add("蜘蛛精"); list.add("至尊宝"); list.add("至尊宝"); list.add("牛夫人"); //2.public void add(int index,E element) 在某个索引位置插入元素 list.add(2,"紫霞仙子"); System.out.println(list); //3.pulbic E remove(int index) 删除当前索引位置处的元素,返回被删除的元素 System.out.println(list.remove(2)); System.out.println(list); //4.public E get(int index) 返回集合中指定位置的元素 System.out.println(list.get(3)); //5.public E set(int index,E element) 修改当前索引位置处的元素,修改成功后,会返回原来的数据 System.out.println(list.set(3, "牛魔王")); System.out.println(list); } }
List集合支持的遍历方式:
- for循环(因为List集合有索引)
- 迭代器
- 增强for
- Lambda表达式
ArrayList
特点:
- 有序、可重复、有索引
底层原理:
-
基于数组实现
优缺点:
- 查询快,增删慢
适合的应用场景:
####LinkedList
特点:
- 有序、可重复、有索引
底层原理:
-
基于双链表实现的
- 链表:
-
双链表:
底层原理:
- 双链表
优缺点:
- 查询慢,增删快,但对首尾元素进行增删改查的速度是极快的
常用方法:
应用场景
-
队列
-
只是在首尾增删(首删尾增)元素,用LinkdeList实现正合适
-
使用:
//1.创建一个队列(queue) //只是在首尾增删元素,用LinkedList实现很合适 //先进先出,后进后出,就是排队 //实际应用:排队,叫号系统 LinkedList<String> queue = new LinkedList<>(); //入队 queue.addLast("第1个人"); queue.addLast("第2个人"); queue.addLast("第3个人"); queue.addLast("第4个人"); System.out.println(queue); //出队 System.out.println(queue.removeFirst()); System.out.println(queue.removeFirst()); System.out.println(queue.removeFirst()); System.out.println(queue);
-
-
栈
-
只是在首部增添元素,用LinkedList实现正合适
-
使用:
//2.创建一个栈对象 //只是在首部增添元素,用LinkedList实现很合适 //先进后出,后进先出 LinkedList<String> stack = new LinkedList<>(); //压栈(push) //push <-> addFirst stack.addFirst("第一颗子弹"); stack.addFirst("第二颗子弹"); stack.push("第三颗子弹"); stack.push("第四颗子弹"); //出栈(pop) //pop <-> removeFirst System.out.println(stack.removeFirst()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack);
-
Set
特点
- 添加的元素是无序、不重复、无索引
*ps:*Set要用到的常用方法,基本上就是Collection提供的,自己几乎没有额外新增一些常用功能
哈希值
-
就是一个int类型(-21亿 ~ 21亿)的数值,Java中每个对象都有一个哈希值
-
Java中的所有对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值
public int hashCode();
-
同一个对象多次调用hashCode()方法返回的哈希值是相同的
-
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)
*ps:*sout打印出来的对象(默认调用toString方法)其实并不是真实物理地址,而是根据对象的哈希值转成十六进制等一系列方法得到的,所以两个对象打印出来显示的“地址”相同,但实际只是哈希值相同并不是真实物理地址相同,所以调用equals方法或者==
判断时依然为false
。
HashSet
特点
- 无序、不重复、无索引
底层原理:
-
基于哈希表实现的
-
哈希表是一种增删改查数据,性能都较好的数据结构
-
JDK8之前,哈希表由数组 + 链表构成
ps:占满
16*0.75=12
个坑数组就扩容2倍,以防止链表过长,导致查询性能降低 -
JDK8开始,哈希表由数组 + 链表 + 红黑树构成
-
-
优缺点:
- 增删改查都较快
ps:
-
如果希望Set集合认为2个对象内容一样就是重复的,必须在该对象对应的类下重写hashCode()和equals()方法
-
对于equals方法:Object、 Objects类的equals()方法都是比较地址(针对引用数据类型),但是Objects多了一层判断是否是null,避免了空指针异常,但其为static静态方法不支持重写;而Object为所有类的父类,equals支持重写,但Objects归根还是调用的Object的equals方法,所以重写对Objects也有影响(?
LinkedHashSet
特点
- 有序、不重复、无索引
底层原理
-
依然是基于==哈希表(数组 + 链表 + 红黑树)==实现的
-
但是,它的每个元素都额外多了一个双链表的机制记录它前后元素的位置
TreeSet
特点
- 排序(默认升序)、不重复、无索引
底层原理
- 基于红黑树实现的排序
ps:
- 对于数值类型:Integer、Double,默认按照数值本身的大小进行升序排序
- 对于字符串类型:默认按照首字母的编号(字典序)升序排序
- 对于自定义类型如Student对象,TreeSet默认是无法排序的,需要自己定义排序规则
- TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持以下两种方式来制定比较规则
- 1.让自定义的类实现Comparable接口,重写里面的compareTo方法来制定比较规则
- 2.通过调用TreeSet集合的有参构造器,可以设置Comparator对象(比较器对象,用于制定比较规则)
- *ps:*如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序
- TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持以下两种方式来制定比较规则
集合的并发修改异常
- 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误
- 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的问题;由于Lambda表达式遍历调用forEach方法本质上也是调用增强for循环,因此,使用Lambda表达式遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的问题
解决方法
-
使用迭代器遍历集合,使用迭代器自己的删除方法删除数据,而不用集合的删除方法删除
List<String> list = new ArrayList<>(); Iterator it = list.Iterator(); /* 集合中增添数据... */ while(it.hasNext()){ if(...){ it.remove();//迭代器当前指向的元素删除,并在底层实现类似于普通for循环i--的类似操作,指向当前位置的新的元素 } }
-
list接口的实现类由于存在索引,故可以使用for循环遍历:
- 倒着遍历并删除
- 从前往后遍历,删除元素后做i–操作
Collections及其相关知识
前置知识:可变参数
- 可变参数就是一种特殊形参,定义在方法、构造器的形参列表里,本质是某个数据类型的数组,格式是:数据类型…参数名称
- 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它
- 好处:可以用来灵活的接收数据
- 注意事项:
- 一个形参列表中,只能有一个可变参数
- 可变参数必须放在形参列表的最后面
Collections
-
用来操作单列集合Collection的工具类
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class CollectionsTest1 {
public static void main(String[] args) {
//掌握Collections集合工具类的使用
//1.public static<T> boolean addAll(Collection<? super T> c, T...elements) 为集合批量添加元素
List<String> names = new ArrayList<>();
Collections.addAll(names,"wxy","xiaoming","xiaohong");
System.out.println(names);
//2.public static void shuffle(List<?> list) 打乱List集合中的元素顺序
Collections.shuffle(names);
System.out.println(names);
//3.public static <T> void sort(List<T> list) 对List集合中的元素进行升序排序
List<Integer> nums = new ArrayList<>();
Collections.addAll(nums,5,35,2,79,1616,8,71,6);
System.out.println(nums);
Collections.sort(nums);
System.out.println(nums);
//4.public static <T> void sort(List<T> list, Comparator<? super T> c )
// 对List集合中元素,按照比较器对象指定的规则进行排序,一般应用于集合中存储自定义类型对象的qing
Collections.sort(nums, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return 0;
}
});
System.out.println(nums);
}
}
Map
- 双列集合,每个元素包含两个值(键值对),格式:{key1=value1,key2=value2…},一次需要存入一对数据作为一个元素
- Map集合的每个元素”key = value“ 被称为一个键值对/键值对对象/Entry对象,Map集合也被叫做==”键值对集合”==
- Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
- 需要存储一一对应的数据时,就可以考虑使用Map集合来存储
Map集合的常见方法
代码:
package d1_map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest2 {
public static void main(String[] args) {
//1.public V put(); 添加元素
Map<String, Integer> map = new HashMap<>();//一行经典代码,无序,不重复,无索引
map.put("鼠标", 100);
map.put("鼠标", 250);//后面的数据会覆盖前面的数据(根据键)
map.put("电脑", 100);
map.put("Java书籍", 1000000000);
map.put(null, null);
System.out.println(map);
//2.public int size();获取集合的大小
System.out.println(map.size());
//3.public void clear();清空集合
// map.clear();
// System.out.println(map);
//4.public boolean isEmpty(); 判断集合是否为空
System.out.println(map.isEmpty());
//5.public V get(Object key) 根据键获取对应值
System.out.println(map.get("Java书籍"));
System.out.println(map.get("Java"));//返回null
//6.public V remove(Object key); 根据键删除整个元素(删除键会返回键的值)
System.out.println(map.remove("Java书籍"));//1000000000
System.out.println(map.remove("Java"));//null
//7.public boolean containsKey(Object key); 判断是否包含某个键
System.out.println(map.containsKey("Java书籍"));//false
System.out.println(map.containsKey("鼠标"));//true
//8.public boolean containsValue(Object value); 判断是否包含某个值
System.out.println(map.containsValue(10000));//false
System.out.println(map.containsValue(null));//true
//9.public Set<K> keySet(); 键不可重复故用Set集合存储,获取Map集合的全部键
Set<String> keys = map.keySet();
System.out.println(keys);
//10.public Collection<V> values(); 值可以重复故用Collection集合存储,获取Map集合的全部值
Collection<Integer> values = map.values();
System.out.println(values);
//11.把其他Map集合的数据倒入到自己集合中去(拓展)
Map<String,Integer> map1 = new HashMap<>();
map1.put("java1",10);
map1.put("java2",20);
HashMap<String, Integer> map2 = new HashMap<>();
map2.put("java2",200000);
map2.put("java3",30);
map1.putAll(map2);//putAll:把map2集合中的元素全部复制粘贴一份到map1集合里面
System.out.println(map1);
System.out.println(map2);
}
}
Map集合的遍历方式
- 键找值
package d2_map_bianli;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
Map集合的遍历方式:
1.键找值
*/
public class MapTest1 {
public static void main(String[] args) {
//准备一个Map集合
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("蜘蛛精",169.5);
map.put("紫霞",167.5);
map.put("至尊宝",177.5);
map.put("牛魔王",172.5);
System.out.println(map);
// {蜘蛛精=169.5, 牛魔王=172.5, 至尊宝=177.5, 紫霞=167.5}
//1.获取Map集合的全部键存入Set集合中
Set<String> keys = map.keySet();
//2.遍历Set集合根据键找值
for (String key : keys) {
Double value = map.get(key);
System.out.println(key + "--->" + value);
}
}
}
- 键值对
package d2_map_bianli;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
Map集合的遍历方式:
2.键值对
*/
public class MapTest2 {
public static void main(String[] args) {
//准备一个Map集合
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("蜘蛛精",169.5);
map.put("紫霞",167.5);
map.put("至尊宝",177.5);
map.put("牛魔王",172.5);
System.out.println(map);
// map = {蜘蛛精=169.5, 牛魔王=172.5, 至尊宝=177.5, 紫霞=167.5}
//1.调用Map集合提供的entrySet()方法,把Map集合转换成键值对类型的Set集合
Set<Map.Entry<String, Double>> entries = map.entrySet();
// entries = {(蜘蛛精=169.5), (牛魔王=172.5), (至尊宝=177.5), (紫霞=167.5)}
//2.遍历Set集合的每个键值对对象,分别调用getKey、getValue取到键、值
for (Map.Entry<String, Double> entry : entries) {
String key = entry.getKey();
Double value = entry.getValue();
System.out.println(key + "--->" + value);
}
}
}
- Lambda
package d2_map_bianli;
import java.util.HashMap;
import java.util.Map;
/**
Map集合的遍历方式:
3.Lambda
*/
public class MapTest3 {
public static void main(String[] args) {
//准备一个Map集合
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("蜘蛛精",169.5);
map.put("紫霞",167.5);
map.put("至尊宝",177.5);
map.put("牛魔王",172.5);
System.out.println(map);
// map = {蜘蛛精=169.5, 牛魔王=172.5, 至尊宝=177.5, 紫霞=167.5}
// map.forEach(new BiConsumer<String, Double>() {
// @Override
// public void accept(String k, Double v) {
// System.out.println(k + "--->" + v);
// }
// });
//Lambda表达式简化代码,源码其实还是调用了键值对遍历的方法
map.forEach((String k, Double v) -> { System.out.println(k + "--->" + v); });
}
}
Student类(后续会使用到)
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
//根据对象的内容生成hashcode值,内容一样hashcode值则一样,
//存放在HashMap集合中所对应的哈希表的位置一样,进而进行equals判断
return Objects.hash(name, age, height);
}
@Override
//调用该方法的this,传入的参数o
public int compareTo(Student o) {
return this.getAge() - o.getAge();
}
}
HashMap
特点
- 无序、不重复、无索引
底层原理:
-
基于哈希表实现的
-
哈希表是一种增删改查数据,性能都较好的数据结构
-
JDK8之前,哈希表由数组 + 链表构成
ps:占满
16*0.75=12
个坑数组就扩容2倍,以防止链表过长,导致查询性能降低 -
JDK8开始,哈希表由数组 + 链表 + 红黑树构成
-
-
优缺点及使用:
ps:
-
如果希望Map集合认为2个对象内容一样就是重复的,必须在该对象对应的类(Student类)下重写hashCode()和equals()方法
- 重写前的hashCode根据对象在内存中所处位置分配hash值,重写后根据对象的内容分配hash值;重写前的equals比较hash值是否相等,重写后的equals比较内容是否相等。
import java.util.HashMap; import java.util.Map; public class Test1HashMap { public static void main(String[] args) { Map<Student,String> map = new HashMap<>(); map.put(new Student("蜘蛛精",18,165.5),"水帘洞"); map.put(new Student("蜘蛛精",18,165.5),"盘丝洞"); map.put(new Student("至尊宝",21,175.5),"水帘洞"); map.put(new Student("牛魔王",24,178.9),"牛头山"); System.out.println(map); } }
LinkedHashMap
特点
- 有序、不重复、无索引
底层原理
import java.util.LinkedHashMap;
import java.util.Map;
public class Test2LinkedHashMap {
public static void main(String[] args) {
Map<String,Integer> map = new LinkedHashMap<>();//有序、不重复、无索引
map.put("手表",100);
map.put("手表",200);
map.put("Java",100000000);
map.put(null,null);
System.out.println(map);
}
}
TreeMap
特点
- 有序、不重复、无索引
底层原理
- 基于红黑树实现的排序
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class Test3TreeMap {
public static void main(String[] args) {
Map<Student,String> map = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(),o2.getHeight());
}
});//排序,不重复,无索引
//Lambda简化
// Map<Student,String> map = new TreeMap<>((Student o1, Student o2) -> { return Double.compare(o1.getHeight(),o2.getHeight()); });//排序,不重复,无索引
map.put(new Student("蜘蛛精",18,165.5),"水帘洞");
map.put(new Student("蜘蛛精",18,165.5),"盘丝洞");
map.put(new Student("至尊宝",21,175.5),"水帘洞");
map.put(new Student("牛魔王",24,178.9),"牛头山");
System.out.println(map);
}
}