集合框架(一)
1.数据结构
Java的集合框架其实是对数据的封装,那么什么是数据结构呢?
我认为,数据结构其实就是计算机中存储的形式不同
数据结构使用来模拟数据的存储操作,其实就是增删改查
1.1常见的数据结构
- 数组(Array)
- 链表(Linked List)
- 哈希表(Hash)
- 栈(Stack)
- 队列(Queue)
- 树(Tree)
- 图(Graph)
- 堆(Heap)
1.2数组结构
数组的特征:数组一旦初始化完成,长度是不再发生改变的,数组在内存中是连续的(物理连续,逻辑连续),
可以通过索引值类找到对应的索引,所以数组的查询效率高,反之添加和删除的效率低,
因为在中间进行添加与删除的操作时,数组元素是需要进行移动的
1.3链表结构
链表结构分为 单向链表与双向链表
1.单向链表:只能从头遍历到尾,从头进,从尾出
2.双向链表: 从头到尾,从尾到头都可以遍历
链表是通过引用上一个节点与下一个节点来表示之间的关系的
单向链表
单向链表只有一条链子连接,只能单向读取下一个结点.
双向链表
链表中有两条链子,有一条专门记录元素的顺序,前驱后继,能够读取前后的结点
链表优缺点:查询,更改慢(查询要从头开始查),添加,删除快
1.4队列
队列是一种特殊的线性表,分为单向队列,双向队列
-
单向队列(Queue):先进先出(FIFO),只能从队尾插入数据,队列头部删除数据
-
双列队列(Deque):可以从队列尾/头插入数据,只能从队列头/尾删除数据
1.5栈
栈(stack)又名堆栈,它是一种运算受限的线性表,后进先出(LIFO)
非线性数据结构(暂时不写)
2.集合框架体系
集合是java提供的一种容器,用来存储多个数据(元素),并且元素只能是对象(引用类型),
根据不同的存储方式所形成的体系就叫集合框架体系
Collection是java集合框架中的顶层接口,定义了增删改查的方法,预定了增删改查规范
2.1容器的分类
1.每一种容器类底层都拥有不同的底层算法,根据容器的存储特点不同,大致分为三种
- List(列表):允许记录添加顺序,可重复(有序可重复)
- Set(集合):无序且唯一
- Map(映射):每一个元素包含一对键对值(key,value),key不可重复,value可重复,严格来说不算容器,只是两个容器元素的映射关系
2.List和Set接口继承于Collection接口,Map接口不继承Collection接口。
3.我们使用的容器接口或类都在java.util
包中
4.注意:集合类中存储的对象,都存储的是对象的引用,而不是对象本身。
3.List接口和实现类
List接口是Collection接口的子接口,该接口的常用实现类有
实现类的命名方式(底层数据结构+实现接口),List实现类必须加引用数据类型,最好保持同一类型
-
ArrayList类:数组列表, ArrayList是List接口的实现类.底层数据结构是数组,
底层数据结构决定了ArrayList的适用场景是给定索引进行查询和修改操作
-
LinkedList类 :链表,表示双向列表,双向队列结构,采用链表实现
-
Stack类 :栈,表示栈结构,采用数组实现
-
Vector类:向量,其实是老版的ArrayList,线程安全高
3.1List常用的API方法
- 添加操作
boolean add(Object e)
:将元素添加到列表的末尾
void add(int index, Object element)
:在列表的指定位置插入指定的元素
boolean addAll(Collection c)
:把c列表中的所有元素添加到当前列表中
- 删除
Object remove(int index)
:从列表中删除指定索引的元素,返回被删除的元素
boolean removeAll(Collection c)
:删除指定列表中的所有元素
- 修改
Object set(int index, Object ele)
:修改指定索引位置的元素,返回被替换的元素
- 查询
int size()
:返回当前列表中元素个数
boolean isEmpty()
:判断列表中是否为空
Object get(int index)
:查询列表中指定索引的元素
Object[] toArray()
:把列表对象转换为Object数组
boolean contains(Object o)
:判断列表是否存在指定对象
3.2ArrayList类
List接口的实现类,底层是一个Object数组,常用于查询与修改操作
1.操作List接口常用方法
import java.util.ArrayList;
import java.util.List;
public class Test01 {
public static void main(String[] args) {
//创建对象
List list = new ArrayList();
//1.追加
list.add("小罗");
list.add("小月");
System.out.println(list);
list.add(1, "520");
System.out.println(list);
System.out.println(list.size());
//addAll,增加一个集合的元素
List list1 = new ArrayList();
list1.add( "123");
list.addAll(list1);
System.out.println(list);
//2.查询 :get
System.out.println(list.get(0));
//3.修改:set
list.set(0, "小轩");
System.out.println(list);
//4.删除
list.remove(3);
System.out.println(list);
//5.其他:size() /isEmpty /contains()
System.out.println(list.isEmpty()); //是否为空
System.out.println(list.size());
System.out.println(list.contains("aaa")); //是否包含aaa
}
}
2.ArrayList深度理解:ArrayList容量与拓容机制
1.构造方法:默认构建10个容量,也可以指定容量
new ArrayList() / new ArrayList(int initCapacity)
2.当我们进行添加元素时,达到了ArrayList默认的容量,会触发自动拓容机制
newCapacity = oldCapacity = oldCapacity/2;
3.当然我们也可以手动拓容
list.ensureCapacity();
4.未来如果明确知道我不会再向容器中添加元素,此时考虑裁剪容量
list.trimToSize();
总结:
ArrayList是可变数组,能自动拓容,一般如果我们知道具体的容量时
一定要使用new ArrayList(int initCapacity),如果不够用可以手动拓容,
当明确知道自己不会再添加元素时,可以裁剪容量
3.3LinkedList类
LinkList是List接口的实现类,底层数据结构是链表,适合添加与删除操作
1.常用方法
- void addFirst(Object e) 将指定元素插入此列表的开头。
- void addLast(Object e) 将指定元素添加到此列表的结尾。
- Object getFirst() 返回此列表的第一个元素。
- Object getLast() 返回此列表的最后一个元素。
- Object removeFirst() 移除并返回此列表的第一个元素。
- Object removeLast() 移除并返回此列表的最后一个元素。
例子
import java.util.LinkedList;
import java.util.List;
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList list =new LinkedList();
//void addFirst(Object e) 将指定元素插入此列表的开头
list.addFirst("小罗");
//void addLast(Object e) 将指定元素添加到此列表的结尾
list.addLast("小东");
System.out.println(list); //[小罗, 小东]
//Object getFirst() 返回此列表的第一个元素
System.out.println(list.getFirst()); //小罗
//Object getLast() 返回此列表的最后一个元素
System.out.println(list.getLast()); //小东
//Object removeFirst() 移除并返回此列表的第一个元素
System.out.println(list.removeFirst());//小罗
//Object removeLast() 移除并返回此列表的最后一个元素
System.out.println(list.removeLast());//小东
}
}
LinkedList 总结
1.LinkedList 底层数据结构是链表,特别适合添加,删除的场景
2.LinkedList 是List接口的实现类,add/remove/set/get
LinkedList 也可以以栈结构来操作,push/pop
3.LinkedList 也是Queue(单向队列)接口的实现类
add/remove/element 会产生异常
offer/poll/peek 返回特殊值null
LinkedList 也是Deque(双向队列)接口的实现
addFirst/addLast, removeFirst/removeLast , getFirst/getLast 会产生异常
offerFirst/offerLast , pollFirst/pollLast , peekFirst/peekLast 返回特殊值null
4.泛型
1.什么是泛型?
-
泛型其实是一种类型参数,主要用于不知道某个类或接口中的数据类型不确定的时候,泛型可以预知的使用未知的类型
-
Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象,
所以在存储各种类型对象时,会发生Java.lang.ClassCastException (类型转换异常)
- 泛型可用到的接口、类、方法中,将数据类型作为参数传递
- 如果不使用泛型,从容器中获取出元素,需要做类型强转,也不能限制容器只能存储相同类型的元素。
泛型拓展
当我们去定义两个不同类型的泛型对象时,在编译期间,所有泛型的信息都会被擦除,即List与List类型在编译后都会变成List类型(原始类型),因此对于运行期的来说,List与List就是同一个类.
java中的泛型的实现方法称为类型擦除,基于这种方法实现的泛型叫作 伪泛型 java中的泛型基本都是在编译器层次中实现
2.自定义泛型
一般在创建对象时,会给未知的类型设置一个具体的类型,当没有指定泛型时,默认类型为Object
定义泛型
修饰符 class 类名<代表泛型的变量> { }
}
//自定义泛型类
public class Fan<T>{
private T x; //定义变量x的类型为泛型
private T y;
//省略getter,setter
}
测试类
public class Test01 {
public static void main(String[] args) {
//创建对象时,指定泛型的类型为Integer
Fan<Integer> integerFan = new Fan<Integer>();
integerFan.setX(10); //设置值
integerFan.setY(20);
System.out.println(integerFan);
//也可以定义String类型的对象,new Fan<>可以省略类型名称,
//因为泛型确定左右两边都相同类型
Fan<String> stringFan = new Fan<>();
stringFan.setX("666");
stringFan.setY("999");
System.out.println(stringFan);
}
}
3.泛型类型引用传递问题
泛型不存在继承的关系
以下代码的引用传递是不允许的
1. ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误
2. ArrayList<Object> list = new ArrayList<String>();//编译错误
分析过程
看第一种情况,我们可以将其拆分
ArrayList<Object> list1 = new ArrayList<Object>();
list.add(new Object());
ArrayList<String> list2 = list1; //编译报错
假设编译没有错,当我们list2引用get()方法取值时,返回的是String类型的对象,
但是此时实际上我们已经存放了Object类型的对象了,所以就会产生类型冲突,为了避免这种情况我们是不允许传递的
看第二种情况,
反过来,Object类型的值的确是可以容纳String类型的值,但是需要我们进行强制转换,
违背了当初我们设计泛型的理念,当初就是为了不想强制转换解决类型转换问题,所以也是不行的