概览
- 常见数据结构
- 大o表示法
- 有序数组和无序数组
- 集合
- Collection
- Set
- HashSet
- LinkedHashSet
- TreeSet
- EnumSet
- List
- ArrayList
- LinkedList(同时继承Deque)
- Vector
- Stack
- Queue
- PriorityQueue
- LinkedList
- BlockingQueue
- Map
- HashTable
- HashMap
- LinkedHashMap
- WeakHashMap
- TreeMap
- Map和Collection的区别
- LinkedHashMap和LinkedHashSet
- 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么
- 集合和数组的区别
- Iterator
- ListIterator
- Collections
- Arrays
常见数据结构
- 树:二叉树是一种非常常用的树形结构
- 图
- 数组:长度固定,可以用下标索引
- 列表:ArrayList
- 栈:peek(),push()和pop() ,先进后出
- 队列:peek(),offer()和poll(), 先进先出
- 链表:每个节点包含有数据以及指向下一个节点的引用,在双向链表里,还会有一个指向前一个节点的引用
- HashMap:散列表
大O
- 大O符号可以表示一个算法的效率,随着数据量的增大,处理效率的变化情况,我们一般使用大O符号基于时间,内存和性能来选择最好的实现
- 在HashMap:O(1)
- 线性搜索一个数组是:O(n),依次遍历
- 选择排序:O(n^2) (冒泡算法)
- 快速排序:O(nlogn),选择一个值,将数据区分为大于该值和小于该值两个部分,再依次对这两个部分重复之前的操作
- 二分搜索:O(log n)
如何权衡是使用无序的数组还是有序的数组?
- 有序数组查找为O(log n),而无序数组是O(n)。
- 有序数组插入是O(n)(因为值大的元素需要往后移动来给新元素腾位置)。无序数组的插入O(1)。
- 所以,查找操作多的时候,使用有序;增删操作多的使用无序的即可。
集合
Collection
- toArray(t[]):在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组,用数组限定了对集合中的元素进行增删操作,只要获取这些元素即可
- 如果是Integer必须:
Integer[] arr=list.toArray(new Integer[list.size()]);
想要转成int[]只能循环一次
- 如果是Integer必须:
set
- 数据不能重复,无序
HashSet:
- 底层用hash表实现(在HashMap的基础上封装:数组加链表的结构,jdk8后有红黑树),数据无序,线程不同步
- 需要实现hashCode() 、equals()
LinkedHashSet
- 除了hash表还有,还有链表数据结构(在使用迭代器的时候可以顺序读取),线程不同步
- 与HashSet一样,相同的数据没有办法重复存入
TreeSet
- 底层是红黑树(平衡二叉树),存储的元素需要实现Comparable(可以叫做自然排序),重写compareTo()方法
- 也可以用Comparator来定义比较的方式(定制排序),重写compare()
EnumSet
- 为枚举类型专门制定的集合
- 一个EnumSet中只能存放同一种枚举类型
public static void main(String[] args){
EnumSet<EnumObj> set=EnumSet.noneOf(EnumObj.class);
set.add(EnumObj.A);
set.add(EnumObj.B);
set.add(EnumObj.A);
System.out.println(set);
}
https://blog.youkuaiyun.com/chengqiuming/article/details/70139107
List
- 数据可以重复,有序,底层使用equals()方法来进行比较
- 主要方法:add() get() remove() contains() indexOf() size() iterator() replaceAll()
- 对于一个Integer类型的list需要注意删除时区分类型:
list.remove(new Integer(1));
list.remove(1);
- 对于一个Integer类型的list需要注意删除时区分类型:
ArrayList
- 底层的数据结构是数组
- 默认大小10,1.5倍长度扩容,查询元素的速度非常快
LinkedList
- 底层的数据结构是双端链表,增删元素的速度非常快
- 实现了Deque中定义的方法,主要是向顶端和底部插入读取数据
- addFirst();addLast():addLast()=add()
- offerFirst();offerLast();offer():offer()=offerLast()
- pollFirst();pollLast():没有返回空,获取后删除;pollFirst()=poll()
- removeFirst();removeLast():没有抛出异常,获取后删除;removeFirst()=remove()
- peekLast();peekFirst():获取但是不删除;peekFirst()=peek()
- push()=addFirst()
- pop()=removeFirst()
- LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用
Vector
- 底层的数据结构就是数组,Vector无论查询和增删都很慢
Stack:栈
Queue
- offer():插入一个元素到队列中,当做队列使用
- poll():从队列头部取出一个元素,失败返回null,当做队列使用,pollFirst()相同
- remove():从队列头部取出一个元素,失败抛异常
- Queue有一个子接口Deque,提供了两个模拟栈的方法:
- push() 进入stack顶部addFirst()
- pop() 从stack顶部取一个元素类似于removeFirst()
PriorityQueue
- 优先级队列,可以通过传入一个comparator来决定优先级,线程不安全。复杂度是O(log(n))
- 其实不是一个真正意义上的队列
- 这个数据结构最大的作用是用来模拟堆(默认情况下是最小堆或称小顶堆,也就是说最小的放在第一个)
PriorityQueue<Integer> priorityQueue=new PriorityQueue<>();
for (int i=0;i<10;i++){
priorityQueue.offer(i);
}
priorityQueue.offer(3);
priorityQueue.offer(5);
System.out.println(priorityQueue);//[0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 4, 5]
System.out.println(priorityQueue.poll());//0(最小的在第一个)
System.out.println(priorityQueue);//[1, 3, 2, 5, 3, 5, 6, 7, 8, 9, 4]
//遍历也不能保证按照递增顺序,或者和插入顺序一直
while(iterator.hasNext()){
System.out.println(iterator.next());
}
LinkedList
- 同时也是一个List
- 这个类可以很好的模拟队列和堆栈
ArrayDeque
- 实现Deque,底层是数组实现
BlockingQueue
- 可以设置队列大小
- put() 如果队列满了,将阻塞等待
- take() 如果队列为空,则等待
- 主要的实现有:LinkedBlockingQueue、ArrayBlockingQueue
Map
- 对Map进行遍历,主要是将数据转成Set(keySet entrySet),再用迭代器
Iterator<String> iterator = school.keySet().iterator();
Iterator<HashMap.Entry<String,List<Student>>> iterator1=school.entrySet().iterator();
- 常用方法:put() get() entrySet() keySet() remove() isEmpty()
Hashtable
- 底层是哈希散列表数据结构,线程同步。不可以存储null键,null值
- 由于使用的是较为粗粒度的锁,所以效率较低
HashMap
- 底层是哈希表数据结构(链地址法解决冲突),线程不同步。可存一个null键和多个null值
- Map m = Collections.synchronizeMap(hashMap)实现同步
- 那就是为何大量使用String和Integer这些不可变类作为HashMap的key(原因就是防止可变类的修改导致再次利用key查找索引的时候不可复现原来的索引,即查找索引失败)。
- 默认大小是16,注意必须是2的指数,和具体的实现有关,方便同key的hash值取与算出具体的位置,保证key能均匀分布
LinkedHashMap
- 相比HashMap来说,能记录数据进入的顺序
- 采用双向链表数据结构连接起来所有的entry
- HashMap的子类
WeakHashMap
- 如果存入的key为弱引用,当key对象没有任何引用的时候,key/value会被回收
TreeMap
- 底层是二叉树结构(平衡二叉排序树),key需要继承Comparable
Map和Collection的区别
- map:一次存一对数据,保证键唯一
- Collection:一次存一个数据
- map不继承collection?尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection无论谁继承谁都毫无意义
LinkedHashSet和LinkedHashMap比较
- LinkedHashSet中有个LinkedHashMap,采用了适配器模式
- LinkedHashMap是HashMap的子集,在HashMap的基础上添加了双向链表,保存数据的顺序
HashMap和TreeMap在性能上有什么样的差别呢
- TreeMap的内部是一个红黑树结构(平衡二叉树),增删改查复杂度是O(log n),但是可以记录下顺序
- HashMap内部是hash表和链表,增删改查复杂度是O(1)
使用无序的HashSet和HashMap,还是使用有序的TreeSet和TreeMap
- 如果要求排序,可以使用红黑树结构
- 如果对于排序操作的要求不是很频繁的话,可以放在hash数据结构中,需要排序的时候再先存入到List中Collections.sort(List)排序
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么
- java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的
- 快速失败:当错误发生时,如果系统立即关闭,即是快速失败,系统不会继续运行。在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改。会抛出异常
- 在ArrayList中,每次获得一个迭代器,会保存modCount,集合变化的次数,每次操作迭代器的时候都会先检查当前的次数和记录下来的次数是否一致,如果不一致直接抛出异常ConcurrentModificationException。
- 安全失败:错误发生时不会停止运行。它们隐蔽错误,继续运行,而不会暴露错误。
- 采用安全失败的容器,例如:CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap中,每次进行操作(add() remove())的时候都会复制一份出来,对复制数组进行操作,同时整个类使用同一个ReentrantLock,保证不会被同时修改。
- 由于复制了一份出来进行操作,所以会有额外的内存消耗。它不能保证你迭代时获取的是最新数据,因为迭代器创建之后对集合的任何修改都不会在该迭代器中更新
https://www.cnblogs.com/ygj0930/p/6543350.html
http://www.cnblogs.com/zrbfree/p/6323422.html
集合和数组的区别
- 长度是否可变
- 数组可以存放基本类型也可以是引用类型,集合只能是引用类型(自动装包)
数组必须是相同的数据类型,集合可以使不同的数据类型都可以存入不同类型的数据,但是不建议这样用
Object[] objArr=new Object[2];
objArr[0]=1;
objArr[1]=new Object4HashSet("ming");
for (Object object:objArr){
System.out.println(object);
}
List<Object> list=new ArrayList<Object>();
list.add(2);
list.add("hello");
for (Object object:list){
System.out.println(object);
}
Iterator
- next() hasNext() remove()删除当前值
- 每一种集合都有自己的数据结构,所以实现上面方法的方式也不同,所以定义的是接口。可以想象这里之所以没有add(),考虑到set并不能保序,所以在迭代过程中新增的数据并不确定存放位置。
- 不能同时使用iterator和集合进行删除
- 使用迭代器更加线程安全,因为它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException。
ListIterator
- 继承自Iterator
- 相比Iterator只能遍历List,可以添加元素
- 可以双方向遍历previous() next(),迭代期间可以增加元素add()、修改元素set()
collections
- 是一个工具类
- 排序:sort
- 二分查找:binarySearch
- 倒序:reverseOrder reverse(List)
- 同步:synchronizedList synchronizedMap
- 返回一个不可改的集合;unmodifiableCollection
Arrays
- 可以将数组装换成List
List<String> list =Arrays.asList(arr);
- 底层是数组,所以不能增删
- 可以使用list提供的方法:contain() indexof() isEmpty()
- 不能直接打印数组,需要使用Arrays.toString()
- Arrays.copyOf(arr,newLength):复制数组,设置新数组长度
- Arrays.copyOfRange(arr,beginIndex,endIndex):复制从beginIndex到endIndex的元素
泛型
- 在集合上添加一个支持的类型,如果添加其他类型在编译时就会报错。这样将运行期的异常前移到编译期。
- 使用泛型后也不在需要进行强制类型转换了,
总结
- 看到Array就是数组结构,有角标,查询速度很快。
- 看到link就是链表结构:增删速度快,而且有特有方法。addFirst; addLast;removeFirst(); removeLast();getFirst();getLast();
- 看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到该结构中的元素必须覆盖hashCode和equals方法。
- 看到tree就是红黑树,就要想到排序,就想要用到比较。
- HashSet、TreeSet、LinkedHashSet的区别:HashSet只去重,TreeSet去重并排序,LinkedHashSet去重并保留插入顺序
- 对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
- 对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。
最佳实践
- 如果数量确定应该用Array而不是ArrayList,Array最高效
- 使用泛型使得提高健壮性
- 如果容量能大概估计出来可以设定合理的值,避免resize
- 定义时,接口优于实现:不要
ArrayList list = new ArrayList(100);
而是:List list = new ArrayList(100);
参考:https://blog.youkuaiyun.com/csdn_terence/article/details/78379878