- 数组的不足
- 长度初始必须指定,一旦指定不能更改
- 保存的必须为同一类型的元素
- 使用数组增加元素的代码比较麻烦
- 集合好处:
- 动态保存任意多个对象
- 使用了一系列方便的操作对象的方法,add、remove、set、get
- 添加删除元素的代码简洁
- 集合
- 集合主要是两组,单列集合和双列集合
- Collection有两个重要子接口,List和Set,它们的实现子类都是单列集合
- Map接口的实现子类是双列集合,存放的K-V
- Collection接口实现类的特点
- 可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复元素,有些不可以
- 有些Collection的实现类,有些是有序的(List),有些不是有序的(Set)
- Collection没有直接的实现子类,而是通过它的子接口Set和List来实现的
- Collection方法
- add添加某个元素
- remove删除某个元素
- contains查找元素是否存在
- size获取元素个数
- isEmpty判断是否为空
- clear清空列表
- addAll添加多个元素
- containsAll查找多个元素是否都存在
- removeAll删除多个元素
- Collections接口遍历元素的方式1-使用Iterator
- Iterator称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
- Iterator仅用于遍历集合,本身并不存放对象
- Iterator的迭代原理
Iterator iterator = coll.iterator(); // 得到一个集合的迭代器
// 判断是否还有下一个元素
while(iterator.hasNext()){
iterator.next();// 作用1.下移,2.将下移以后集合位置上的元素返回
}
- 显示所有快捷键(win)ctrl+j (mac)command+j
- 退出while循环后迭代器指向最后的元素,如果想再次遍历,需要重置迭代器
iterator = col.iterator();
- Collections接口遍历元素的方式2-增强for
- 可以用在集合和数组上
for (Object obj : collection) {
System.out.println(obj);
}
- 底层还是迭代器,可以理解成简化版迭代器
- List接口(Collection的子接口)
- List集合类中元素有序(添加顺序和取出顺序一致),且可重复
- List集合中的每个元素都有其对应的顺序索引,即List支持索引
- add(index, object)在index位置插入object
- addAll(index, collection)在index位置插入collection的所有元素
- get(index)获取指定index位置的元素
- indexOf(object)返回object在集合中首次出现的位置
- lastIndexOf(object)返回object在集合中最后一次出现的位置
- remove(index)移除指定位置的元素
- set(index, object)设置指定index位置的元素为object,相当于替换
- subList(fromIndex, toIndex)返回fromIndex <= 子集合 < toIndex位置的子集合
- 遍历方式:1.iterator 2. 增强for 3. 普通for
- ArrayList注意事项
- 可以加入null
- 底层是由数组来存储的
- ArrayList基本等同于Vector,除了是线程不安全的,执行效率高(多线程情况下不建议使用ArrayList)
- ArrayList底层结构和源码分析
- ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData
- transient表示瞬间、短暂的,表示该属性不会被序列化
- 创建ArrayList对象时,如果使用的是无参构造器,则初始elementData为0;第一次添加则扩容elementData为10,如需再次扩容,扩容elementData为1.5倍。
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小;如需扩容,则直接扩容elementData为1.5倍
- 扩容调用的是Arrays.copyOf()
- Vector底层结构和源码分析
- 底层是一个对象数组,object[] elementData
- 是线程同步,操作方法基本都带有synchronized
- 需要线程同步安全时建议使用Vector
- LinkedList
- 底层实现了双向链表和双端队列特点
- 可以添加任意元素,包括null
- 线程不安全,没有实现同步
- 底层维护一个双向链表,first和last分别指向首节点和尾节点
- 元素添加和删除不是通过数组完成的,相对来说效率较高
- 模拟一个简单的双向链表
LinkedList linkedList = new LinkedList();
linkedList.add(100);
linkedList.add("hello");
linkedList.remove();
linkedList.getFirst();
- ArrayList vs LinkedList
- 改查操作较多,使用ArrayList
- 增删操作较多,使用LinkedList
- 一般来说在程序中,80%-90%都是查询,大部分情况下选择ArrayList
- 根据业务灵活选择
- Set接口
- 无序,添加和取出的顺序不一致,没有索引,添加的顺序和取出的顺序不一致,但是取出的顺序是固定的
- 不允许有重复元素,最多包含一个null
- 是Collection的子接口,常用方法和Collection一样
- 遍历方式:1.使用迭代器 2. 增强for 3.
不能使用索引的方式来获取
- HashSet实现类
- 是set的实现类
- 底层是HashMap
- 可以存放null,但是只能有一个null
- 无序,不保证取出顺序和添加顺序一致
- 不能有重复元素/对象
- add方法添加元素,返回boolean值,是否添加成功
Set set = HashSet();
set.add(new Dog("tom")); //t
set.add(new Dog("tom")); //t
set.add(new String("tom")); //t
set.add(new String("tom")); //f
- HashSet底层模拟(模拟HashMap)
- HashSet底层是HashMap,HashMap底层是数组+单向链表+红黑树(链表满足一定量时且数组在某个范围之内,就会把链表进行树化)
// 模拟简单的数组+链表
// 创建一个数组,数组类型是Node[]
// 有些人把Node[] 称为表
// 考虑效率
Node[] table = new Node[16];
Node rose = new Node(null, "rose");
Node jack = new Node(rose, "jack");
Node john = new Node(jack, "john");
table[2] = john;
System.out.println(table);
- HashSet底层机制
- HashSet底层是HashMap
- 添加一个元素时先获取元素的Hash值,然后转成索引值。
- 找到存储数据table表,看这个索引位置是否已经存放元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同就放弃添加;如果不相同就添加到最后。
- JDK8中,一条链表元素个数大于等于8(TREEIFY_THRESHOLD),并且table的大小大于等于64(MIN_TREEIFY_CAPACITY),就会进行树化。
- HashSet扩容机制
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值是16*0.75 = 12
- 如果table数组使用到了12,就会扩容到16 * 2 = 32,新的临界值就是32* 0.75 = 24,依此类推
- Java8中,一条链表的 元素个数到8,且table大小到64,就会进行树化,否则仍然采用数组扩容机制。
- LinkedHashSet
- 是HashSet子类
- LinkedHashSet底层是LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashcode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- 不允许添加重复元素
- 加入顺序和取出的顺序一致
- 第一次添加时,table数组扩容到16,存放的结点类型是LinkedHashMap$Entry
- 数组是HashMap$Node,存放的数据类型是LinkedHashMap$Entry
- 每一个节点有before和after属性,这样可以形成双向链表
- 序列化和反序列化
经常听到实现Serializable接口就可以进行序列化,那么序列化到底是什么?
序列化:将数据结构or对象转为二进制字节流的过程
反序列化:二进制字节流转为数据结构or对象的过程
- 序列化一般在OSI七层协议的表示层,TCP/IP四层协议的应用层
- JDK自带的序列化,只需要实现java.io.Serializable
- SerialVersionUID用于版本控制。序列化时会和数据一起写入二进制序列。反序列时会检查是否和当前类的serialVersionUID一致,不一致会抛出InvalidClassException异常。
推荐手动指定SerialVersionUID
,否则编译器回动态生成默认的序列化号。 - 很少使用JDK自带的序列化方式。常用的序列化协议:hessian、kyro、protostuff
- kryo是专门针对java语言序列化方式且性能非常好。