2019独角兽企业重金招聘Python工程师标准>>>
集合框架(collections framework)
现实生活中:很多相同事物凑在一起,比如人群
数学中的集合:具有共同属性的事物的总体
java中的集合框架:是一种工具类,就像是容器,储存任意数量的具有共同属性的对象
其实说白了,可以把一个集合看成一个微型数据库,操作不外乎“增删改查”四种操作,我们在学习使用一个具体的集合类时,需要把这四个操作的时空复杂度弄清楚了,基本上就可以说掌握这个类了。
两大基类Collection与Map
在集合框架的类继承体系中,最顶层有两个接口:
- Collection表示一组纯数据
- Map表示一组key-value对
一般继承自Collection或Map的集合类,会提供两个“标准”的构造函数:
- 没有参数的构造函数,创建一个空的集合类
- 有一个类型与基类(Collection或Map)相同的构造函数,创建一个与给定参数具有相同元素的新集合类
因为接口中不能包含构造函数,所以上面这两个构造函数的约定并不是强制性的,但是在目前的集合框架中,所有继承自Collection或Map的子类都遵循这一约定。
Collection
如上图所示,Collection类主要有三个接口:
- Set表示不允许有重复元素的集合(A collection that contains no duplicate elements)
- List表示允许有重复元素的集合(An ordered collection (also known as a sequence))
- Queue JDK1.5新增,与上面两个集合类主要是的区分在于Queue主要用于存储数据,而不是处理数据。(A collection designed for holding elements prior to processing.)
Collection接口部分源码
public interface Collection extends Iterable {
int size();
boolean isEmpty();
boolean contains(Object o);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection> c);
boolean addAll(Collection extends E> c);
boolean removeAll(Collection> c);
void clear();
Iterator iterator();
Object[] toArray();
}
以上是Collection
接口的常用方法,因为3个子接口都继承了这个接口,因此在它们各自特有方法外,都会实现以上方法。
Map
Java 中有四种常见的Map实现——HashMap, TreeMap, Hashtable和LinkedHashMap:
- HashMap就是一张hash表,键和值都没有排序。
- TreeMap以红黑树结构为基础,键值可以设置按某种顺序排列。
- LinkedHashMap保存了插入时的顺序。
- Hashtable是同步的(而HashMap是不同步的)。所以如果在线程安全的环境下应该多使用HashMap,而不是Hashtable,因为Hashtable对同步有额外的开销,不过JDK 5之后的版本可以使用conncurrentHashMao代替HashTable。
说到Map接口的话大家也许在熟悉不过了。Map接口实现的是一组Key-Value的键值对的组合。 Map中的每个成员方法由一个关键字(key)和一个值(value)构成。Map接口不直接继承于Collection接口(需要注意啦),因为它包装的是一组成对的“键-值”对象的集合,而且在Map接口的集合中也不能有重复的key出现,因为每个键只能与一个成员元素相对应。
另外,Set接口的底层是基于Map接口实现的。Set中存储的值,其实就是Map中的key,它们都是不允许重复的。
Map接口部分源码
public interface Map {
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
void putAll(Map extends K, ? extends V> m);
V remove(Object key);
void clear();
Collection values();
Set keySet();
Set> entrySet();
interface Entry {
K getKey();
V getValue();
V setValue(V value);
}
}
源码中的方法大家也比较熟悉。在遍历Map时,我们可以通过keySet()
方法获取到所有key,它返回的是一个Set对象,遍历Set,通过key获取value。也可以通过entrySet()
的方式获取Entry的Set,遍历Set,通过Entry的getValue()
和getKey()
方法获取值和对象,这些在后面会详细讲到。
List接口
List接口对Collection进行了简单的扩充,因此它继承了Collection接口。其中大部分方法和其继承的Collection相同,至于不同之处也不太常用,大家可以参考源码。
特点:
List中存储的元素是有序的,而且可以重复的存储相关元素。
ArrayList
特点:
ArrayList的底层使用数组实现,当元素的数量超过数组长度时,通过新建更大容量数组,将原数组内容拷贝一份,然后新增元素的方式存储元素。
优点:
类似数组的形式进行存储,因此它的随机访问速度极快。
缺点:
不适合于在线性表中间需要频繁进行插入和删除操作。因为每次插入和删除都需要移动数组中的元素。
可以这样理解ArrayList就是基于数组的一个线性表,只不过数组的长度可以动态改变而已。
对于ArrayList的详细使用信息以及创建的过程可以查看jdk中ArrayList的源码,这里不做过多的讲解。
对于使用ArrayList的开发者而言,下面几点内容一定要注意啦,尤其找工作面试的时候经常会被问到。作者去年面试的时候,都已经被问烦了
注意啦!!!!!!!!
1、关于扩容问题:
默认ArrayListde的默认构造函数ArrayList()
会构造一个长度为10的数组。ArrayList(int initialCapacity)
构造函数会构造一个指定长度的数组。
添加元素时,如果超出了长度,则以每次旧长度的3/2倍增长。
例:
new ArrayList(20);
扩容几次?
答案: 0次,因为直接产生了一个长度20的数组
2、ArrayList是线程不安全的,在多线程的情况下不要使用。
如果一定在多线程使用List的,你可以使用Vector,因为Vector和ArrayList基本一致,区别在于Vector中的绝大部分方法都使用了同步关键字修饰,这样在多线程的情况下不会出现并发错误哦,还有就是它们的扩容方案不同,ArrayList是通过原始容量*3/2,而Vector是允许设置默认的增长长度,Vector的默认扩容方式为原来的2倍(可以通过构造函数设置,如设置为2,扩容后长度为旧长度+2)。
切记Vector是ArrayList的多线程的一个替代品。
3、ArrayList实现遍历的几种方法
ArrayList list = new ArrayList();
list.add("hello");
list.add(",");
list.add("world");
// 第一种遍历方式使用foreach遍历List,编译器编译时,会将这种方式转化为迭代器方式
for (String str : list) {
System.out.print(str);
}
System.out.println();
// 第二种遍历方式使用for循环依次得到元素
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i));
}
System.out.println();
// 第三种遍历方式使用迭代器遍历
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next());
}
System.out.println();
LinkedList
特点:
LinkedList的底层用双向链表实现。另外建议阅读其源码,并不难,会让你有醍醐灌顶的感觉。
优点:
链表相对于实现ArrayList的数组来说,其存储空间是散列的而不是连续的,因此在链表中间插入和删除元素时,无需移动后面的元素,只需要改变3个节点的关联即可。
缺点:
因为LinkedList不是空间连续的,因此随机读取时,需要从头到尾的读取,因此不如ArrayList来得快。另外,在使用双向链表实现时,需要额外提供空间供记录前驱节点和后继节点的地址,消耗了额外空间。
对于使用LinkedList而言,下面几点内容一定要注意啦
注意啦!!!!!!!!
1、LinkedList和ArrayList的区别和联系
主要从底层实现、优缺点(随机读取、新增、删除)等方面总结,详见之前总结的特点、优缺点,不再赘述。
2、LinkedList的内部实现
强烈建议大家去看看源码,其内部使用双链表实现,如果你实在不想看,下面的代码提供了其节点结构,和最常用的add()
和remove()
方法。
// 长度
transient int size = 0;
// 节点的表示
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
// 此段不是源码,仅供理解思想
private void add(Node node, E data) {
Node newNode = new Node(node.prev, data, node);
newNode.prev.next = newNode;
node.prev = newNode;
size++;
}
// 此段不是源码,仅供理解思想
private E remove(Node node) {
node.next.prev = node.prev;
node.prev.next = node.next;
size--;
return node.item;
}
3、LinkedList不是线程安全的
注意LinkedList和ArrayList一样也不是线程安全的,如果在对线程下面访问可以自己重写LinkedList
然后在需要同步的方法上面加上同步关键字synchronized
4、LinkedList的遍历方法
同ArrayList
LinkedList list = new LinkedList();
list.add("hello");
list.add(",");
list.add("world");
// 第一种遍历方式使用foreach遍历List
for (String str : list) {
System.out.print(str);
}
System.out.println();
// 第二种遍历方式使用for循环依次得到元素,这种方式用到了随机读取,特别特别不推荐
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i));
}
System.out.println();
// 第三种遍历方式使用迭代器遍历
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next());
}
System.out.println();
}
5、LinkedList可以被当做堆栈来使用
由于LinkedList实现了接口Dueue,所以LinkedList可以被当做堆栈来使用,这个你自己研究吧。
Vector
Vector和ArrayList不论在实现,还是使用上,都大同小异。因此也就不细说,他们主要的不同就是Vector是线程安全的,它在一些方法上加了synchronized
关键字。
划重点了!!!!!!!!
1、Arraylist与Vector的区别
- Vector是线程安全的,ArrayList不是线程安全的。
- ArrayList在底层数组不够用时在原来的基础上扩展0.5倍(3/2),Vector是扩展1倍。
参见 https://zhuanlan.zhihu.com/p/28241176
Set接口
Set接口也是Collection接口的一个常用子接口,它区别于List接口的特点在于:
Set中的元素实现了不重复,有点象集合的概念,无序,不允许有重复的元素,最多允许有一个null元素对象。
需要注意的是:虽然Set中元素没有顺序,但是元素在set中的位置是有由该元素的HashCode决定的,其具体位置其实是固定的。
此外需要说明一点,在set接口中的不重复是由特殊要求的。
举一个例子:对象A和对象B,本来是不同的两个对象,正常情况下它们是能够放入到Set里面的
但是
如果对象A和B的都重写了hashcode和equals方法,并且重写后的hashcode和equals方法是相同的话。那么A和B是不能同时放入到Set集合中去的
也就是Set集合中的去重和hashcode与equals方法直接相关。
Set接口的常见实现类有HashSet,LinedHashSet和TreeSet这三个,现在依次介绍这三个类:
HashSet
HashSet是Set接口的最常见的实现类了。其最底层是通过Hash表(一个元素为链表的数组)实现的。另外,Hash表底层依赖的两个方法hashcode与equals方法,想存入HashSet的元素需要复写这两个方法。
为什么说其最底层呢?请先看下这段源码:
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable {
private transient HashMap map;
public HashSet() {
map = new HashMap<>();
}
}
显而易见,HashSet的内部是基于HashMap实现的,我们都知道在HashMap中的key是不允许重复的,你换个角度看看,那不就是说Set集合吗?
我们只需要用一个固定值值代替Map中的value,
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
jdk中用了一个静态的Object对象代替了value。那Set中的元素做key便可以保证元素不重复。
下面讲解一下HashSet使用和理解中容易出现的误区:
1、HashSet中存放null值
HashSet中时允许出入null值的,但是在HashSet中仅仅能够存入一个null值哦。
2、 HashSet中存储元素的位置是固定的
HashSet中存储的元素的是无序的,这个没什么好说的,但是由于HashSet底层是基于Hash算法实现的,使用了hashcode,所以HashSet中相应的元素的位置是固定的哦。
3、 遍历HashSet的几种方法
HashSet hashSet = new HashSet();
hashSet.add("hello,");
hashSet.add("hello,");
hashSet.add("world");
// 第一种遍历方式,使用foreach遍历
for (String str : hashSet) {
System.out.print(str);
}
System.out.println();
// 第二种遍历方式,使用迭代器遍历
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next());
}
LinkedHashSet
LinkedHashSet不仅是Set接口的子接口而且还是上面HashSet接口的子接口,和HashSet由HashMap实现一样,LinkedHashSet的底部由LinkedHashMap实现。
Set set = new HashSet();
set.add("hello");
set.add("world");
for (String str : set) {
System.out.print(str);
}
System.out.println();
set = new LinkedHashSet();
set.add("hello");
set.add("world");
for (String str : set) {
System.out.print(str);
}
上面的程序输出结果如下:
可见,LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
因为它底层由LinkedHashMap实现,所以更多细节参加LinkedHashMap。
TreeSet
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。
向TreeSet中加入的应该是同一个类的对象。TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)
方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法
由于水平和时间有限,Map相关内容暂不总结
https://www.cnblogs.com/xiohao/p/4309462.html
https://zhuanlan.zhihu.com/p/24338517?utm_source=wechat_session&utm_medium=social