集合框架
为什么要使用集合框架?
1.数组的长度固定
2.数组无法同时存储多个不同的数据类型
集合简单的理解就是一个长度可以改变,可以保持任意数据类型的动态数组,集合本身就是数据结果的基本概念之一,我们这里所说的集合就是java语言对这种数据结果的具体实现。
Java中的集合不是由一个类来完成的,而是由一组接口和类构成了一个框架体系,大致可以分为3层,最上层是一组接口,继而是接口的实现类
接口
Collection:集合框架最基础的接口,最顶层的接口。
List:Collection的子接口,存储有序、不唯一(元素可重复)的对象,最常用的接口
Set:Collection的子接口,存储无序,唯一(元素不可重复)的对象
Map:独立于Collection的另外一个接口,最顶层的接口,存储一组键值对象,提供键到值的映射
Iterator:输出集合元素的接口,一般适用于无序集合,从前往后输出
ListIterator:Iterator子接口,可以双向输出集合中的元素
Enumeration:传统的输出接口,已经被Iterator取代。
SortedSet:Set的子接口,可以对集合中的元素进行排序
SortedMap:Map的子接口,可以对集合中的元素进行排序
Queue:队列接口
Map.Entry:Map的内部接口,描述Map中存储一组键值对元素
Collection接口
Collection是集合框架中最基础的父接口,可以存储一组无序,不唯一的对象。
Collection接口可以存储一组无序,不唯一(可重复)的对象,一般不直接使用该接口,也不能被实例化,只是用来提供规范。
Collection是Iterable接口的子接口。其中包含如下常用方法
Collection常用子接口
- List:存放有序、不唯一的元素
- Set: 存放无序,唯一的元素
- Queue:队列接口
List接口
List常用的拓展方法
List接口的实现类
ArrayList是开发中使用频率最高的List实现类,实现了长度可变的数组,在内存中分配连续的空间,所以读取快,增删慢
/**
* java.util.list 接口 extends Collection 接口
* List接口的特点:
* 1.有序的集合,存储元素和取出元素的顺序是一致的(存储123,取出123)
* 2.有索引,包含了一些带有索引的方法
* 3.允许存储重复的元素
* * List接口中带索引的方法(特有)
* - public void add(int idex,E element); 将指定的元素,添加到该集合中指定的位置上
* -public E get(int index); 返回集合中指定位置的元素
* -public E remove (int index); 移除列表中指定位置的元素,返回的是被移除的元素
* -public E set(int index,E element);用指定元素替换集合中指定位置的元素,返回值的更新前的元素
* * 注意:操作的时候一定要防止索引越界
* */
public class Demo01List {
public static void main(String[] args) {
List<String> list = new ArrayList<>() ;
list.add("a");
list.add("b");
list.add("c");
list.add("d");
System.out.println(list);
//在c和d之间添加一个元素
list.add(3,"hg");
//移除列表中指定位置的元素,返回的是被移除的元素
String remove = list.remove(2);
System.out.println(remove);
//用指定元素替换集合中指定位置的元素,返回的是更新前的元素
String d = list.set(3, "D");
System.out.println(d);
System.out.println(list);
//list集合遍历有三种元素
for (int i = 0; i <list.size() ; i++) {
//返回集合中指定位置的元素
String s = list.get(i);
System.out.println(s);
}
//使用迭代器进行遍历
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
System.out.println(next);
}
//使用简化版的迭代器,增强for循环
for (String s : list) {
System.out.println(s);
}
}
}
List接口的常用实现类
ArrayList:底层是基于数组的实现,非线程安全,效率高,查询快,增删慢,所有的方法都没有synchronized修饰,它的底层支持动态扩容,每次存储空间不够的时候,他都会将空间自动扩容到1.5倍大小,注意:(扩容操作涉及到内存申请和数据的搬移,是比较耗时的),所以如果事先能确定存储的数据大小最好
1.创建ArrayList时采用默认的构造函数创建集合,然后往里面添加元素,第一次添加时将数组扩容到10的大小,之后添加元素都不会在扩容,直到第11次添加然后扩容1.5倍取整,此后如果需要扩容都是1.5倍取整,但是扩容的最大值是Integer.Max.VALUE
2.每次扩容都会创建一个新的数组,然后将原数组中的数据搬移到新的数组中,ArrayList无法存储基本类型的数据,比如int 、long 需要封装成为包装类,扩容有一定的性能消耗,所以如果特别关注性能,或者希望使用基本类型,就可以选用数组
Vector:线程安全,效率低,实现线程安全直接通过synchronized修饰方法来实现的
Stack:Vector的子类,实现了栈的数据结构,(后进先出)
- push:入栈方法
- peek:取出栈顶元素,将栈顶元素复制一份,取完之后栈内的元素不变
- pop:取出栈顶元素,直接取出栈顶元素,取完之后栈内的数据减一
linkedList:实现了先进先出的队列,非线程安全的,底层采用双向链表的形式存储,那摩什么是链表呢?链表是一种在物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,链表由一系列节点(链表中每一个元素称为一个节点)组成,节点可以在运行时动态生成,每个节点包括两个部分,一个是存储数据元素的数据域,另一个是存储下一个节点的指针域,链表与数组相反,他并不需要一块连续的内存空间,他通过指针将一组零散的内存块串联起来使用,特点:查询慢,增删快
面试题ArrayList和LinkedList之间有什么区别?
1.两者在内存中存储的形式不同,ArrayList底层基于数组实现,LinkedList底层基于双重链表实现的
数组在内存中存储空间是连续的
链表在内存中的存储空间是非连续的
2.对于随机访问,ArrayList优于LinkedList,ArrayList可以根据下标对元素进行随机访问,而LinkedList的每一个元素都依靠地址指针和他的后一个元素连接在一起,在这种情况下,查找某一个元素只能从链表头开始查询,直到查找到为止
3.对于插入和删除操作,LinkedList由于ArrayList,因为当元素被添加到LinkedList任意位置时,不需要像ArrayList那样重新计算大小或者是更新索引
4.LinkedList比ArrayList更占内存,因为LinkedList节点除了存储数据,还存储了两个指针引用,一个指向前一个元素,一个指向后一个元素
面试题:LinkedList和Stack都有pop方法,有什么区别和相同点?
pop方法都是取出集合中的第一个元素,但是两者的顺序是相反的,Stack是“后进先出”,所以pop取出的是最后一个元素,LinkedList是先进先出,所以pop取出的是第一个元素。
LinkedList实现了Deque接口,而Deque接口是Queue的子接口,Queue就是队列,底层实现了队列的数据结构
实际开发中,不能直接实例化Queue对象。
Queue的实现类是AbstractQueue,他是一个抽象类,不能直接实例化,开发中需要实现它的子类PriorityQueue.
Queue中添加的数据必须是有序的
Set
跟List一样,Set是Collection的子接口,Set集合是以散列的形式存储数据,所以元素是没有顺序的,可以存储一组无序且唯一的数据
Set常用实现类:
- HashSet
- LinkedHashSet
- TreeSet
HashSet是开发经常使用的一个实现类,存储一组无序且唯一的对象
无序:元素的存储顺序和遍历顺序不一致
public class Test {
public static void main(String[] args) {
HashSet set = new HashSet();
// 添加元素
set.add("Hello");
set.add("Spring");
set.add("SpringBoot");
set.add("Java");
// 移除元素
set.remove("Java");
}
}
LinkedHashSet是Set的另外一个实现类,可以存储一组有序且唯一的元素
有序:元素的存储顺序和遍历顺序一致
equals和==的区别?
所有类中的equals都是继承自Object类,Object类中原生的equals方法就是通过进行判断
但是每个类都可以对equals方法进行重写,覆盖掉之前使用==进行判断的逻辑,改用新的逻辑进行判断是否相等。
LinkedHashSet如何判断两个对象是否相等?
首先会判断两个对象的hashCode是否相等
什么是hashCode?
将对象的内部信息(内存地址,属性值等),通过某种规则转换成一个散列值,就是该对象的hashCode.
- 两个不同对象的hashCode值可能相等
- hashCode不相等的两个对象一定不是同一个对象
集合在判断两个对象是否相等的时候,会先比较他们的HashCode,如果hashcode不相等,则认为不是同一个对象,可以添加。
如果hashcode值相等,还不能认为两个对象是相等的,需要通过equals进行进一步判断,equals相等,则两个对象相等,否则两个对象不相等
:判断的是栈内存中的值
引用类型的数据,栈内存中存储的是地址,所以此时==判断的是引用地址
基本数据类型,栈内存中存储的是具体的数值
TreeSet
LinkedHashSet和TreeSet都是存储一组有序且唯一的数据,但是这里的两个有序是有区别的。
LinkedHashSet的有序是指元素的存储顺序和遍历顺序是一致的。
6,3,4,5,1,2----》6,3,4,5,1,2
TreeSet的有序是指集合内部会自动对所有的元素按照升序进行排列,无论存入的顺序是什么,遍历的时候一定按照升序输出
自定义数据类型
public class TreeSetTest2 {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Data(1));
treeSet.add(new Data(3));
treeSet.add(new Data(6));
treeSet.add(new Data(3));
treeSet.add(new Data(5));
treeSet.add(new Data(4));
treeSet.add(new Data(1));
System.out.println("treeSet的长度是"+treeSet.size());
}
}
class Data implements Comparable{
private int num;
public Data(int num) {
this.num = num;
}
/**
* A.compareTo(B)
* 返回值:
* 1 表示A大于B
* 0 表示A等于B
* -1 表示A小于B
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
if (o instanceof Data){
Data data =(Data)o;
if (this.num>data.num){
return 1;
}else if (this.num==data.num){
return 0;
}else {
return -1;
}
}
return 0;
}
}