集合是一种容器,用来装数据的,类似于数组,但集合的大小可变。
集合体系结构
集合分为单列集合(Collection)和双列集合(Map)
Collection代表单列集合,每个元素(数据)只包含一个值
Map代表双列集合,每个元素包含两个值(键值对)
集合与数组的区别
数组 | 集合 | |
长度区别 | 长度固定 | 长度可变 |
内容区别 | 基本类型、引用类型 | 只能是引用类型 |
元素区别 | 只能存储同一种类型 | 可以存储不同类型 一般存储的也是同一种类型 |
Collection集合体系
Collection集合特点
Collection是一个接口类,分为以下两个系列
List系列集合:
添加的元素是有序、可重复、有索引
Set系列集合:
添加的元素是无序、不重复、无索引
Collection的常用方法
方法名 | 说明 |
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
Collection的遍历方式
1、迭代器
迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。
Collection集合获取迭代器的方法
方法名称 | 说明 |
Iterator<E> iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素 |
Iterator迭代器中的常用方法
方法名称 | 说明 |
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象指向下一个元素处。 |
Iterator<String> it = lists.iterator();
while(it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
注意:
集合的并发修改异常
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
怎么保证遍历集合同时删除数据时不出bug?
使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。
2、增强for循环
- 增强for可以用来遍历集合或者数组。
- 增强for遍历集合,本质就是迭代器遍历集合的简化写法。
注意:修改增强for循环中的变量值不会影响到集合中的元素
格式:
for (元素的数据类型 变量名 : 数组或者集合) {
}
Collection<String> c = new ArrayList<>();
...
for(String s : c) {
System.out.println(s);
}
3、Lambda表达式
方法名称 | 说明 |
default void forEach(Consumer<? super T> action) | 结合lambda遍历集合 |
Collection<String> lists = new ArrayList<>();
...
lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//Lambda写法
lists.forEach(s -> {
System.out.println(s);
});
// lists.forEach(s -> System.out.println(s));
Collection补充
可变参数
就是一种特殊参数格式,格式是:数据类型... 参数名称;
可变参数的特点和好处
特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
好处:常常用来灵活的接收数据
可变参数注意事项
可变参数在方法内部就是一个数组。
一个形参列表中可变参数只能有一个
可变参数必须放在形参列表的最后面
Collections工具类
Collections 类在java.util包中,是一个操作集合的工具类。
Collections 类提供了许多操作集合的静态方法,可以实现集合元素的排序、批量添加,替换等操作。
常用方法
addAll(Collection<T> c, T... elements) 将所有指定的元素添加到指定的集合c中。
shuffle(List<?> list) 随机打乱list集合中元素的顺序。
sort(List<T> list) 根据自然顺序对list集合的元素进行升序排序。
sort(List<T> list, Comparator<T> c) 根据指定的比较器,对list集合元素进行自定义排序。
public class Test1 {
public static void main(String[] args) {
//addAll(Collection<T> c, T... elements) 将所有指定的元素添加到指定的集合c中。
ArrayList<Integer> list = new ArrayList<>();
//批量添加
Collections.addAll(list,11,22,33,44);
System.out.println(list);//shuffle(List<?> list) 随机打乱list集合中元素的顺序。
Collections.shuffle(list);
System.out.println(list);//sort(List<T> list) 根据自然顺序对list集合的元素进行升序排序(从小到大)。
Collections.sort(list);
System.out.println(list);
}
}
List集合
List是一个接口类
List系列集合特点:有序、可重复、有索引
List<String> list = new ArrayList<>();
当里面没有定义泛型时<String>,会默认使用Object父类,当使用时,必须强转才能调用该类的特有方法。
List接口特有方法
方法名称 | 说明 |
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
addAll(另一个集合) | 集合的拷贝 |
...其他方法 | Collection接口方法 |
List使用remove方法问题
因为集合的大小可变,使用remove方法移除一个元素时,索引和长度发生变化,
——会出现删除不干净的问题
public class Demo1 {
public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
list.add("d");
list.add("b");//删除集合的所有b元素
//有问题的代码:删除时索引会改变
/*for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if(s.equals("b")){
list.remove(i);
}
}*///正确的方式1:删除成功后,索引减1
/*for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if(s.equals("b")){
list.remove(i);
i--;
}
}*///正确方式2:倒序遍历(推荐)
for(int i=list.size()-1; i>=0; i--){
String s = list.get(i);
if(s.equals("b")){
list.remove(i);
}
}//一直删除,直到返回false,说明已经删完了
/*while (true){
boolean b = list.remove("b");
if(b==false){
break;
}
}*///简化写法
/*while ( list.remove("b") ){}*/
System.out.println(list);
}
}
List集合遍历方式
1、for循环
List系列集合——ArrayList
面试热点问题:
1.ArrayList底层是什么数据结构?
Object类型的数组
Object[] elementData;2.底层的数组何时创建,初始化长度是多少?
使用无参构造方法创建集合,默认把elementData数组初始化为{}数组(长度为0)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
使用带参构造方法创建集合,数组的长度就是参数的值。3.首次调用add方法添加元素的时候,数组的长度变为多少?
首次添加的时候,长度会由0进行扩容。首次扩容后,长度变成10
if (s == elementData.length){
elementData = grow();
}4.如果添加的元素超过数组长度,怎么办?
如果元素超过长度,会再次扩容,扩容后长度是多少?
长度会在原来基础上增加一半 :新的长度 = 旧的长度 + 旧的长度/2; 10 —> 15 ->22
int newCapacity = oldCapacity + (oldCapacity >> 1); 相当于 int newCapacity = oldCapacity + (oldCapacity /2);
>> 右移符号(2进制的计算)
5. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
ArrayList特点:
ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同。
删除效率低:可能需要把后面很多的数据进行前移。
添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。
应用场景:
List系列集合——LinkedList
LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
LinkedList的底层是一个双向链表,增删快,查询慢。
双向链表可以快速对首尾元素进行操作,所以LinkedList中有很多进行首尾操作的API。
链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
链表的特点:
1、查询慢,无论查询哪个数据都要从头开始找。
2、链表增删相对快
LinkedList常用方法
方法名称 | 说明 |
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
...其他方法 | Collection中方法 |
Set集合
Set集合特点:
无序:添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引;【统称】
HashSet : 无序、不重复、无索引。
LinkedHashSet:有序、不重复、无索引。
TreeSet:排序、不重复、无索引。
常用方法
注意:
Set要用到的常用方法,基本上就是Collection提供的!!
自己几乎没有额外新增一些常用功能!
Set遍历方式
使用Set集合时的情况
存储自定义类型的数据如对象等时,即使对象内的属性值相等,也是会存储到集合中,因为集合存储的是对象的地址值。如果想要去重,要重写hashCode()和equals()方法。
哈希值
就是一个int类型的数值,Java中每个对象都有一个哈希值。
Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
public int hashCode(); 返回对象自己的哈希值
对象哈希值的特点:
同一个对象多次调用hashCode()方法返回的哈希值是相同的。
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
Set系列集合——HashSet
HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性
特点:
存储的元素是无序不重复无索引的。
底层数据结构是哈希表,具有良好的存储和查找性能。
HashSet集合没有索引,只能通过迭代器或增强for循环或者forEach遍历集合。
常用方法:
boolean add(E e) | 将指定的元素添加到此集合(如果尚未存在)。 |
boolean remove(Object o) | 如果存在,则从该集合中删除指定的元素。 |
int size() | 返回此集合中的元素数。 |
boolean contains(Object o) | 如果此集合包含指定的元素,则返回 true 。 |
Iterator<E> iterator() | 返回此集合中元素的迭代器 |
HashSet底层结构——哈希表
哈希表:JDK8之前,哈希表 = 数组 + 链表
JAK8开始, 哈希表 = 数组 + 链表 + 红黑树
哈希表存储元素的过程:
创建一个默认长度16,默认加载因为0.75的数组,数组名table
根据元素的哈希值跟数组的长度计算出应存入的位置 (存入位置= 哈希值 % 数组的长度)
判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素, 则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
Set系列集合——LinkedHashSet
LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高
特点:有序(根据链表记录存入的顺序)、不重复、无索引
底层结构——哈希表 + 双链表
Linkedlist里面是一个双向链表,每个空间存有了前和后对象的地址。使得,每次调用数据时,可以有顺序存入!遍历时只找头节点即可按顺序进行。
⚫ LinkedHashSet继承了HashSet,底层在哈希表的基础上,又维护了一个双向链表。
⚫ LinkedHashSet通过双向链表实现了元素的存取有序性。
Set系列集合——TreeSet
特点:不重复、无索引、可排序(默认升序排序 ,按照元素的大小,由小到大排序)
底层结构——红黑树
注意:
对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet默认是无法直接排序的,需要指定排序规则。
TreeSet自然排序
TreeSet集合可以对实现Comparable接口的元素自动排序。
步骤:
1. 元素类需要实现Comparable接口,重写 compareTo() 方法。
2. 使用无参构造方法创建TreeSet集合,集合会对添加元素的实现排序。
3.注意当比较元素不int,返回不是int时。 可以通过判断,直接return 1 、0、-1。
CompareTo 只看的是,大于0、小于0 或者是等于0 不看具体值。
//让学生类实现Comparable接口,具备排序能力
public class Student implements Comparable<Student>{
private String name;
private int age;/*
this :准备存入集合的元素
Student s :已经存在集合中的元素
返回值大于0,认为要存入的元素比较大,存到右边
返回值小于0,认为要存入的元素比较小,存到左边
返回值等于0,认为要存入的元素已存在,不会存入
*/
@Override
public int compareTo(Student s) {
//return this.age - s.age; //根据年龄升序排序
return s.age - this.age; //根据年龄降序排序//如果年龄一样,比较姓名,如果姓名也一样不存入
/*int result = this.age - s.age;
if(result==0){
if(this.name.equals(s.name)){
return 0;
}else {
return 1; //返回不是0的数字就可以
}
}else {
return result;
}*/
}
//Set ,get 方法
compareTo方法排序原理
⚫ 如果返回值为负数,认为当前存入的元素是较小值,存左边。
⚫ 如果返回值为正数,认为当前存入的元素是较大值,存右边。
⚫ 如果返回值为0,认为两个元素一样,不存入集合。
TreeSet比较器排序
没有实现Comparable接口的元素,无法实现自动排序。
此时可以在TreeSet的构造方法中传入Comparator比较器,实现排序。
当既有Comparable接口接口排序和Comaparator比较器时,以Comparator比较器为主。
Comparator比较器排序:
⚫ 使用TreeSet的带参构造方法创建集合对象。
⚫ TreeSet的构造方法必须接收Comparator的实现类对象,并重写compare(T o1, T o2)方法。
⚫ compare方法的参数o1表示要添加的元素, o2表示集合中的元素,返回值规则如下。
速记: 1到2 升序。
o1 – o2 升序排序
o2 – o1 降序排序
如果返回值为负数,认为当前存入的元素是较小值,存左边。
如果返回值为正数,认为当前存入的元素是较大值,存右边。
如果返回值为0,认为两个元素一样,不存入集合。
实现方式:匿名内部类:直接在方法内new Comparator
/*
使用TreeSet,要求元素是具备排序的能力的,即要求元素实现Comparable接口或者要求TreeSet的构造方法接收一个比较器对象,即Comparator接口的实现类对象
*/
public class TreeSetDemo2 {
public static void main(String[] args) {
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",15);
Student s3 = new Student("王五",21);
Student s4 = new Student("赵六",21);
Student s5 = new Student("赵六",21);//比较器排序
//如果元素实现Comparable接口,而同时又有比较器Comparator,此时比较器优先
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge(); //年龄升序
//return o2.getAge() - o1.getAge(); //年龄降序
}
});ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);for(Student s : ts){
System.out.println(s);
}
}
}
两种比较方式小结
⚫ 自然排序:自定义类实现Comparable接口,重写compareTo方法。
⚫ 比较器排序: TreeSet带参构造方法传入Comparator的实现类对象,重写compare方法。
⚫ 当两种排序规则同时出现时, TreeSet集合会使用比较器进行排序。
Map集合
Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , ...}, 一次需要存一对数据做为一个元素.
Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对集合”
Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
Map集合体系的特点
HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引。
使用Map集合时的情况
存储自定义类型的数据如对象等时,即使对象内的属性值相等,也是会存储到集合中,因为集合存储的是对象的地址值。如果想要去重,要重写hashCode()和equals()方法。
Map集合常用方法
方法名称 | 说明 |
public V put(K key,V value) | 添加元素 |
public int size() | 获取集合的大小 |
public void clear() | 清空集合 |
public boolean isEmpty() | 判断集合是否为空,为空返回true , 反之 |
public V get(Object key) | 根据键获取对应值 |
public V remove(Object key) | 根据键删除整个元素 |
public boolean containsKey(Object key) | 判断是否包含某个键 |
public boolean containsValue(Object value) | 判断是否包含某个值 |
public Set<K> keySet() | 获取全部键的集合 |
public Collection<V> values() | 获取Map集合的全部值 |
留意一个方法:
put():当key值相同时候,value值会替换,新的值会放存在,Map集合里面,但返回的时旧值:
遍历方式
1、键找值
先获取Map集合全部的键,再通过遍历键来找值
方法名称 | 说明 |
public Set<K> keySet() | 获取所有键的集合 |
public V get(Object key) | 根据键获取其对应的值 |
1.获取Map中所有的键,方法提示: keySet()
2. 遍历键的Set集合,得到每一个键
3. 根据键,获取键所对应的值。方法提示: get(K key)
Set<String> keySet = map.keySet(); for (String key : keySet) { System.out.println(key + "===>" + map.get(key)); }
2、键值对
把“键值对“看成一个整体进行遍历(难度较大)
Map提供的方法 | 说明 |
Set<Map.Entry<K, V>> entrySet() | 获取所有“键值对”的集合 |
Map.Entry提供的方法 | 说明 |
K getKey() | 获取键 |
V getValue() | 获取值 |
把每一个key和value值封装成为一个对象。
Map.Entry<K,V>接口
Map中将每个键和值封装成一个个的Entry<K, V>对象,
并提供 getKey() 和 getValue() 方法,用于获取Entry中封装的键和值。
Set<Map.Entry<String, Double>> entries = map.entrySet(); for (Map.Entry<String, Double> entry : entries) { System.out.println(entry.getKey() + "===>" + entry.getValue()); }
3、Lambda
//forEach方法 map.forEach(new BiConsumer<String, Double>() { @Override public void accept(String key, Double value) { System.out.println(key + "===>" + value); } }); System.out.println("-----------------------------------------"); map.forEach((key,value) -> { System.out.println(key + "===>" + value); });
Map系列集合——HashMap
HashMap特点
无序、不重复、无索引
HashMap是非线程安全的
实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
⚫ HashMap底层是哈希表结构+链表/红黑树。
⚫ 依赖hashCode方法和equals方法保证键的唯一。
⚫ 如果键要存储的是自定义对象,需要重写hashCode和equals方法。 在key的类里面重写了HashCode和equals,自己定义的书籍类型才能保证基本数据类的唯一性。
map.put(s1, "广州");
map.put(s2, "深圳");
map.put(s3, "杭州");
//s4和s3两个键一样,所以s4没有存入,但是覆盖了s3的值
map.put(s4, "北京");
即通过重写了hashCode和equals方法(依据时他们的年龄和名字):判断了S3 和 S4是否一样。
Integer、String内部已经实现了,出现相同的key值时,会直接覆盖掉原来的数值。(Java内部类已经实现重写)。
Map系列集合——LinkedHashMap
特点:
(由键决定)有序、不重复、无索引
底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)。
实际上:原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap
Map系列集合——TreeMap
TreeMap的特点
(由键决定特点)按照键的大小默认升序排序、不重复、无索引
自动去重
◼ TreeMap是Map里面的一个实现类。
◼ TreeMap底层是红黑树结构;可以对元素的键进行排序。(TreeSet底层就是使用TreeMap实现存储的)
◼ 排序方式有两种:自然排序,比较器排序。
TreeMap的排序
⚫ 自然排序: 使用无参构造器创建TreeMap集合,存入集合的键需要实现Comparable<T>接口。
⚫ 比较器排序: 使用带参构造器创建TreeMap集合,传入Comparator<T>接口的实现类。
TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});