集合
集合不能存储基本数据类型及java对象,只能存储对象的引用
集合分为两大类
一、单个方式存储元素
Collection超级接口
二、键值对存储方式
Map超级接口
-
Collection
继承Iterable可迭代的,拥有迭代器
List接口
有序可重复:放进去是这个顺序,出来也是这个顺序。元素有下标,可以重复。
1.ArrayList
底层是数组,非线程安全;初始化容量为10,每次扩容为原容量的1.5倍;建议预估一个初始化容量,减少数组扩容次数。增删改较慢,查询速度较快
2.LinkedList
底层是双向链表,非线程安全;空间上储存不连续;增删改较快,查询较慢
3.Vector
底层是数组,线程安全;所有方法都用Synchronized修饰,一般不用
补充1:LinkedList与ArrayList异同?
线程安全:LinkedList与ArrayList都是不同步的,也就是不保证线程安全。
底层数据结构:LinkedList采用双向链表,ArrayList采用Object数组。
插入和删除是否受位置影响:ArrayList使用的数组,插入和删除时间复杂度受位置影响。LinkedList使用链表,不受影响,时间复杂度近似O(1),而数组的时间复杂度近似O(n)。
是否支持快速随机访问:LinkedList不支持高效随机访问,ArrayList通过元素的序号快速获取元素对象。
内存占用:ArrayList浪费空间体现在会在尾部预留一定空间,LinkedList每个元素的占用的空间比ArrayList多,因为要存储当前节点的前驱后继信息。
补充2:RandomAccess 接口
查看源代码发现里面什么都没有定义,它是可以快速随机访问的标识,用来表示实现这个接口的类具有随机访问功能。其内因还是底层使用了数组,而不是因为有这个标识所以支持随机访问。
对list遍历方式总结:
1.实现了RandomAccess 接口的list, 优先使用普通for循环,其次是foreach.
2.未实现RandomAccess 接口的list,优先选择iterator遍历(foreach底层也是通过iterator实现的),大size的数据千万不要使用普通for循环。 -
Set接口
无序不可重复:放进去是这个顺序,出来不一定是这个顺序。元素没有下标,不可以有相同的元素。
1.HashSet
创建时实际上是new了HashMap,哈希表数据结构。初始化容量是16,扩容后是原容量的两倍
2.SortedSet
放在该集合中的元素自动排序。
TreeSet继承自SortedSet,底层实际是TreeMap。采用二叉树数据结构
-
Queue接口
代表先进先出的集合。
1.PriorityQueue优先级队列
2.ArrayQueue双端队列
优先级队列 -
Map
key、value键值对存储方式,存储的都是java对象的内存地址;无序不可重复。
1.HashMap
线程不安全的;
可以使用一个null作为key,可以多个null作为value
初始化容量为16,默认加载因子0.75。每次扩容必须是2的幂。
HashMap通过key的hashcode经过扰动函数处理后得到hash值。所谓扰动函数就是HashMap的hash方法,用来防止一些实现比较差的hashcode方法,也就是减少hash碰撞。
元素存放位置:Length是HashMap的长度
index = hash & (Length-1)
JDK1.8之前
基于数组+链表实现,底层维护一个Entry数组。
链表的时间复杂度是O(N)
发生hash冲突将KV键值对放到链表头节点,扩容时会改在链表上的顺序,导致链表成环。
JDK1.8之后
基于数组+链表+红黑树,底层维护一个Node数组。
单项链表节点超过8个,单链表转换为红黑树;红黑树节点少于6个时,转换为单链表。红黑树的时间复杂度是O(logN),优化了二叉树查询树的缺陷,二叉查找树在某些情况下会退化成线性结构,提高了查找效率。
发生hash冲突将KV键值对放到链表尾部,解决了扩容成环的问题,即解决了查询的时候出现死循环的情况
put操作:
1.先对hashcCode()做hash,再计算index
2.如果没有碰撞放入node数组中。
3.如果碰撞了,将key用equals的方法与链表的元素进行比较,如果有true则进行覆盖,如果全为false则插入链表尾部。
4.如果碰撞导致链表过长,就会把链表转化为红黑树
5.如果bucket满了(超过当前最大容量 * 负载因子),就要进行resize
get操作
输入key做一次哈希映射,得到Node数组下标。如果当前位置什么都没有,返回null;如果有链表进行equals比较,有ture返回则value值,全为false则返回null,
O(n),如果为红黑树,O(log n)
在进行比较的时候都是使用equals方法,在重写euqals方法是必须要重写hashcode方法,保证相同对象返回相同的hash值。
2.Hashtable
哈希表数据结构,线程安全。所有方法都带有synchronized修饰。
Properties继承自Hashtable,线程安全。存储采用key-value,只能是String
3.LinkedHashMap
是Hash Map的子类,保存记录的插入顺序,在使用Iterator遍历时,得到的是先插入的;也可以在构造方法是带参数,定义按照访问次数排序。
4.TreeMap
实现SortedMap接口,可以根据key排序,默认是升序,也可以指定比较器。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap时传入自定义的Comparator,否则会在运行时抛出异常。当使用Iterator遍历TreeMap时,得到的记录是排过序的;如果使用排序的映射,建议使用TreeMap。
补充:ConcurrentHashMap
- 1.8之前
使用分段数组+链表实现,一个ConcurrentHashMap里面有多个Segment,每个Segment里面是一个HashEntry数组,HashEntry数组用来存储键值对数据。
每个Segment有一把锁,它实现了RenntrantLock,是一种可重入的锁。要对HashEntry中的数据进行修改时,必须获得Segment锁。
get
先定位到Segment,再通过hash定位到具体的HashEntry上
HashEntry中的value都是使用volatile修饰的,保证了内存的可见性,每次拿到的value都是最新值。get的整个过程没有加锁
put
1.先考虑是否需要扩容
2.定位在Segment位置,再插入到HashEntry数组中
插入过程中先会进行一次hash,确定Segment位置,如果Segment还没有初始化,就会使用CAS对它进行赋值。第二次操作定位在HashEntry数组中的位置,在这个过程中会进程锁的特性。在插入要到HashEntry的时候,它会使用继承的ReentrendLock的tryLock方法去获取锁,如果已经有线程获取了Segment锁,它会以自旋的方式接着调用tryLock去尝试获取锁,在尝试一定次数不成功后就会被挂起,等待唤醒。 - 1.8之后
底层使用Node数组+链表+红黑树实现实现,与HashMap1.8类似。
不再使用Segment锁,而是采用CAS+Synchronized锁来保证并发安全。Synchronized锁只锁定当前链表或红黑树的首节点,只要不发生hash冲突就不会产生并发。
IO流
输入输出流分为两种:字符流(只能读取文字)、字节流
以Stream结尾的都为字节流
以Reader/Writer结尾的都为字符流
文件专属:
FileInputStream
FileOutputStream
FileReader
FileWriter
转换流(将字节流转换为字符流):
InputStreamReader
OutputStreamWriter
缓冲流专属:
BufferedReader
BufferedWriter
BufferedInputStream
BufferedOutputStream
缓冲流自带缓冲区,不需要定义byte数组缓存
数据流专属:
DataInputStream
DataOutputStream
标准输出流:
PrintWriter
PrintStream
使用:
1.创建标准输出流对象,参数为需要输出的东西
2.System.setOut,用来选择流输出的位置
对象专属流:
ObjectInputStream
ObjectOutputStream
使对象序列化到硬盘,或者从硬盘中反序列出来