1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器
说明:此时的存储,主要指的是内存层面的存储,不涉及持久化的存储(.txt,.jpg……)
2.数组在存储多个数据方面的特点
3.数组在存储多个数据方面的缺点
|—Collection
|---List:动态数组
|---Set:集合
|—Map:函数y=f(x)
在java.util包中,可以存放对象的容器
集合中只能存放对象
集合中存放的是多个对象的引用,对象本身还是存放在堆内存中
集合中可以存放不同类型、不限数量【size():int】的数据类型的数据,如果不使用泛型约束存储数据的类型,则默认Object
集合类是由两个接口派生出来的:Collection接口和Map接口
用于遍历集合的工具,集合对象可以通过Iterator去遍历访问集合中的所有元素
List有一个ListIterator专门遍历List,提供双向遍历功能
public interface Iterator<E> {
boolean hasNext();//判断集合中是否存储下一个元素,如果有返回true,否则false
E next();//获取集合中的下一个元素,同时指针后移
default void remove() {//删除集合中上一次next方法返回的元素
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {//用于针对lambda表达式的方式访问集合中的元素
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
Collection接口继承于Iterator接口,要求所有的Collection接口的实现类中提供Iterator接口的实现。
ArrayList中的Iterator方法的实现
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;//实现快死异常,不允许遍历集合的同时,有其他线程修改集合结构
//不能一边遍历一边对元素进行修改
Set<String> set = new HashSet<>();
for(int i =0; i< 10; i++){
set.add("set-"+i);
}
Iterator<String> it = set.iterator();
while(it.hasNext()){
String tmp = it.next;
sout(tmp);
// set.remove(tmp);//ConcurrentModificationException--modCount
it.remove();//删除刚访问过的元素
}
如何解决
ListIterator继承于Iterator接口,支持双向迭代访问,主要是针对List实现可以通过调用ListIterator方法获取
boolean add(Object obj)//添加一个对象
boolean addAll(Collection c)//将一个集合中的所有对象添加到此集合
void clear()//清空此集合中的所有对象
boolean contains(Object o)//检查此集合中是否包含o对象
boolean equals(Object o)//比较此集合是否与指定对象相等
boolean isEmpty()//判断此集合是否为空
boolean remove(Object o)//在此集合中移除o对象
int size()//返回此集合中的元素个数
Object[] toArray()//将此集合转换成数组
Iterator iterator()//迭代集合中的所有元素
//遍历集合中的所有元素
for(Object tmp:collection){}
//使用迭代器
Iterator it = collection.iterator();
while(it.hasNext()){
Object tmp = it.next();
}
集合中每个元素都有一个顺序索引,允许通过索引访问指定位置上的集合元素
List接口继承于Collection接口,同时提供了关于索引相关的操作
sort(Comparator<? super E> c)//用于针对集合中的元素按照c比较器进行排序,底层实现是通过Arrays.sort(a,(Comparator) c);
void add(int index, Object o)//在index位置上插入对象o
boolean addAll(int index, Collection c)//将一个集合中的元素添加到集合中的index位置
Object get(int index)//返回集合中指定位置的元素
List subList(int fromIndex, int toIndex)//返回fromIndex和toIndex之间的集合元素
Object remove(int index)//删除指定位置上的元素,并返回删除的数据
Object set(int index, Object obj)//修改指定位置上的元素,并返回原始数据
boolean addAll(Collection c)//将一个集合中的所有对象添加到此集合
int indexOf(Object obj)//查找元素的下标位置
底层实现是一个可变长数组【java中的数组都是定长的】,允许插入null值,默认初始化容积为10,默认采用的是延迟加载的方式进行初始化数组操作,随着元素的不断增加,集合会调用grow()进行扩容处理,增长扩容比例为50%。
transient Object[] elementData
ArrayList()内部存储数据的数组为空数组
elementData保存数据
capacity容器 ArrayList(int) 默认容积10
private static final int DEFAULT_CAPACITY = 10;
modcout快速失败
无参构造器
new ArrayList(),JDK1.8采用的是延迟数组初始化操作elementData={},这是一种针对内存消耗的优化处理
带参构造器
new ArrayList(18) 初始化容积 capacity和size
添加元素时首先仅从范围检查,然后和建议分配的大小值进行比较,如果大于默认大小则进行扩容。扩容时首先将原始数组的大小提升到1.5倍,称为新数组大小,然后进行元素的拷贝
删除元素,但是没有变小容积的处理
线程安全的替代方案:Collections.sychronizedList CopyOnWriteArrayList
首先进行索引序号[0,size()-1]检查,然后判定是否有并发修改[快速失败处理modcount],最后按照下标从数组中获取元素
底层实现是一个双向链表,没有长度限制【int size()】
不能随机访问,当通过下标访问时会判断具体first和last的远近,从而决定是从头还是从尾开始逐一访问[p=p.next p=p.prev]
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
删除remove(Object)虽然删除操作时间复杂度O(1),但是定位元素的时间复杂度为O(n)
底层实现是一个可变长数组,大部分方法上进行synchronized同步处理,所以线程安全,但会影响并发访问性能还有加锁的代价,所以不再推荐使用。默认初始化容积为10,默认立即执行数组的初始化操作。扩容增长比例为100%
stack继承于Vector,实现了一个后进先出的堆栈,基本方法:push()压栈和pop()弹栈,建议使用Deque(双向队列的方式取代)
ArrayList | LinkedList | Vector | |
---|---|---|---|
实现方式 | 底层实现是数组 | 底层实现是双向链表 | 底层实现是数组 |
查询增删 | 擅长随机访问get和set,非同步 | 擅长增删add和remove,非同步 | 查询快,增删慢 |
线程安全 | 线程不安全、没有同步处理,并发访问效率高 | 线程不安全、没有同步处理,并发访问效率高 | 线程安全、同步处理,并发访问效率较低 |
全部继承Collection中的方法,没有新方法,对add、equals和hashCode方法添加了限制
当添加的两个对象类型元素的hashCode值相等时,才会调用equals判定相等
如果hashCode值不相等,则不会调用equals方法
允许使用null元素
具体实现类有HashSet、LinkedHashSet和TreeSet
底层是HashMap实现的【利用key特性,value值是一个常量值】,无序
private transient HashMap<E,Object> map;//具体存储数据的实现
private static final Object PRESENT == new Object();//向HashSet中存储数据实际上是利用HashMap存储Key-Value的方式进行存储,其中key是HashSet中存储的数据,value是一个常量对象PRESENT
HashSet采hash算法来存储集合中的元素,因此具有良好的读写性能
调用add方法向HashSet中添加元素时,首先针对对象的hashCode值进行比较。HashCode值相等——>继续调用equals方法。
定义类时,如果equals为true,则hashCode必须相等,反之不一定。
HashSet方法没有同步约束,所以线程不安全
HashSet允许添加null元素
boolean contains(Object)
Object[] toArray():放回包含所有元素的数组;如果需要指定返回数组的类型,可以使用toArray(类型[])
boolean add(Object)
boolean remove(Object)
HashSet的底层实际上就是HashMap,只不过 HashSet是实现了Set接口,并且把数据作为key值,而具体的value值一直使用一个相同的虚值来实现的 private static final Object PRESENT = new Object();
public boolean add(E e){
return map.put(e,PRESENT)==null;//调用hashmap中的put方法添加数据,其中添加的元素是key值,具体的value是hashset中定义一个常量
}
由于HashMap的key值不允许重复,并且在HashMap中如果出现key值相同时,会使用新的value覆盖旧有的value,然后返回旧有的value值,那么在hashset中执行这句话就会返回一个false,表示插入失败,这样就能保证了数据的不可重复性
LinkedHashSet继承于HashSet,底层基于LinkedHashMap实现的,底层采用的是链表和哈希表的算法,使用链表记录元素的添加位置,使用哈希表保证元素的唯一性
LinkedHashSet也是根据hashCode值来决定元素的具体存储位置,但是同时它引入了一个链表可以维护元素的插入顺序
有序【插入序和访问序】
基于排列顺序实现元素不重复
实现了SortedSet接口,对集合元素自动排序
元素对象的类型必须实现Comparable接口,指定排序规则。TreeSet是通过CompareTo方法或compare判断大小和排序,确定是否为重复元素与hashCode和equals无关
TreeSet是一种排序的Set集合
底层实际上就是TreeMap实现的,本质上是一个红黑树原理
HashSet | LinkedHashSet | TreeSet | |
---|---|---|---|
无序(不仅不能保证元素的插入顺序,而且元素以后的顺序也可能发生改变),底层采用的是哈希算法,查询效率高 | LinkedHashSet是HashSet的子类,底层采用的是哈希算法以及链表实现,既能保证元素的添加顺序,同时也保证了查询性能。但是由于需要额外维护链表结构,所以整体性能要略低于HashSet | TreeSet不保证元素的添加顺序,但是会对集合中的元素进行排序。底层采用的是红黑树的实现,但是由于需要定位存储位置,所以增删的效率较低。 | |
存储的元素不允许重复(equals和hashCode) | 如果访问的顺序和插入的顺序一致,使用LinkedHashSet | Comparable接口或者自定义Comparator | |
是否用到hashCode()和equals() | √ | √ | × |
要求存入HashSet中的元素必须覆盖定义equals()和hashCode()两个方法,判断元素相等需要依赖这两个方法 | 要求存入HashSet中的元素必须覆盖定义equals()和hashCode()两个方法,判断元素相等需要依赖这两个方法 | 使用的是compareTo和compare进行比较大小和排序 | |
性能分析 | HashSet和TreeSet是Set集合中使用最多的集合,HashSet的性能总是比TreeSet集合的性能好 | LinkedHashSet需要额外维护链表以实现记录元素的插入顺序,因此在插入时性能比HashSet低 |
1.使用Set集合可以剔除集合中的重复元素
Random r = new Random();
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add(r.nextInt(20));
}
System.out.println(list);
Set<Integer> set = new LinkedHashSet<>(list);
System.out.println(set);
2.读取一个英文文档,获取其中有哪些字符(去重复)
BufferedReader br = new BufferedReader(new FileReader("abc.txt"));
Set<Character> set = new HashSet<Character>();
String str = null;
while ((str=br.readLine())!=null) {
for (int i = 0; i < str.length(); i++) {
char cc = str.charAt(i);
set.add(cc);
}
}
System.out.println(set);
Map接口与Collection接口没有任何关系,也是一个顶级接口
数组+链表+红黑树
存储一对数据(Key+Value)
public interface Map<K,V>{}
定义map对象时建议指定key和value对应的类型,key和value要求必须是复杂类型,不能采用int之类的简单类型
map接口中有一个内部接口Entry,每个Entry对象用于封装一对key/value,value允许修改,但是key不允许修改
key值不允许重复,value值没有要求。添加数据时key值重复则后盖前
V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值
Object get(Object key)//根据键获取对应的值
int size()
Object get(Object key)//根据键获取对应的值
…
HashMap底层实现采用的是拉链法的哈希表结构,具体实现 JDK1.7采用【数组+链表】,JDK1.8采用【数组+链表+红黑树】。是为了快速查找而设计的,内部定义了一个Node[]数组用于存储数据,元素会通过哈希函数将key转换为数组中存放的索引值,如果有冲突【key值不相同但是映射位置相同】则采用单向链表存储数据;如果单向链表长度过长则会影响查询效率,所以大于树化阈值,可以将链表转换为红黑树;红黑树的平衡处理比较繁琐,所以当红黑树中节点个数小于退化阈值时会自动转换为单向链。
默认初始化数组长度为16,加载因子为0.75【减少hash碰撞的概率】,树化阈值为8和树化总节点数64,退化阈值6。当元素个数大于【容积*加载因子】时会进行扩容,扩容比例为增加100%。如果设置初始化容积值会自动转换为2的n次方【大于等于初始值】,数组扩容会涉及rehash计算。插入数据时采用的是尾插法
线程不安全,在多线程并发操作中会出现rehash操作出现死循环、脏读导致的数据丢失和size值不精确等问题
存储数据允许null值和null键,但是作为key时null只能出现一次
遍历数据:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable,Serializable
具体的内部数据存储方式
transient Node<K,V>[] table;//哈希表的本质就是一个数组,数组中的每一个元素称为一个桶,桶里存放的是一个key-value组成的链表或者红黑树
LinkedHashMap是HashMap的子类,存储方式与HashMap一致,引入一个双向链表记录数据的顺序
顺序分为【插入序和访问序】
允许null键和null值
非同步线程不安全
由于需要维护元素的顺序,所以性能略低于hashMap性能,但是采用插入序迭代访问全部元素时性能较好
特别适合作为缓存的实现
内部实现是红黑树,存储元素时会根据key值进行排序【自然排序和定制排序】
因为会排序,所以要key值实现Comparable接口或者自定义
判断key值相等使用的是compareTo或者compare方法,不是equals
自定义类充当key一般要求实现Comparable接口并定义equals方法。要求当key值equals为true时,必须compareTo返回为0,避免二义性
基于【数组+链表】实现数据存储,没有要求初始化容积值必须为2^n,默认初始化容积值为11,负载因子:0.75,扩容后容器为【旧有容积*2+1】
HashMap | LinkedHashMap | TreeMap | Hashtable | |
---|---|---|---|---|
key、value==null? | key、value可以为null,但key作为null只能出现一次 | key、value可以为null,但key作为null只能出现一次 | key、value不允许为null(要不无法排序啊) | key、value不允许为null |
安全问题 | 非同步线程不安全(在多线程并发操作中会出现rehash操作出现死循环、脏读导致的数据丢失和size值不精确) | 非同步线程不安全 | 非同步线程不安全 | 同步处理,线程安全 |
总结 | 基于拉链法实现的散列表,一般用于单线程 | 在HashMap存储数据的基础上添加了额外的双向链表记录数据的访问顺序 | 要求按照key值进行排序,底层是红黑树 | 不推荐使用 |
Arrays:操作数组的工具类
Collections:操作集合的工具类
java.util.Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法。Collection接口在java类中有很多的具体实现,Collection接口的作用就是为所有具体实现提供了最大化的统一操作方式,直接子类常见的有List、Set和Queue
java.util.Collections是一个包装类,包含了各种有关集合操作的静态多态方法,类不能实例化,用于实现对集合中的元素进行排序、搜索以及线程安全等操作
有关集合的学习:记接口 记接口中的方法 记实现类的区别
Collection接口 | List接口 | Set接口 | Map接口 | |
---|---|---|---|---|
无序、无下标、元素可以重复 | 有序(维护插入数据的顺序)、有下标(通过索引访问集合元素)、元素可以重复 | 无序(不维护插入数据的顺序),无下标(根据元素本身访问)、元素不可重复 | 无序、无下标、键不可重复、值可重复 | |
实现类 | ArrayList、LinkedList、Vector | HashSet、LinkedHashSet、TreeSet | HashMap、LinkedHashMap、TreeMap、Hashtable |
数组和集合的比较
数组 | 集合 |
---|---|
定长 | 变长存储数据的容器,容量可以动态改变 |
不是面向对象的 | 弥补了数组的缺陷,比数组更灵活 |
存放简单类型和引用类型的数据 | 存放的都是对象的引用 |
无法判断存储的元素个数,length:容器的大小 | size:元素的个数 |
仅采用顺序表的方式 | 有多种不同的实现方式和不同的适用场景 |
以类的形式存在,具有三大特征 |