现实生活中:不少相同事物凑在一块儿,好比人群
数学中的集合:具备共同属性的事物的整体
java中的集合框架:是一种工具类,就像是容器,储存任意数量的具备共同属性的对象html
其实说白了,能够把一个集合当作一个微型数据库,操做不外乎“增删改查”四种操做,咱们在学习使用一个具体的集合类时,须要把这四个操做的时空复杂度弄清楚了,基本上就能够说掌握这个类了。java
在集合框架的类继承体系中,最顶层有两个接口:node
通常继承自Collection或Map的集合类,会提供两个“标准”的构造函数:面试
由于接口中不能包含构造函数,因此上面这两个构造函数的约定并非强制性的,可是在目前的集合框架中,全部继承自Collection或Map的子类都遵循这一约定。算法
如上图所示,Collection类主要有三个接口:数据库
Collection接口部分源码数组
public interface Collection<E> extends Iterable<E> { 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<E> iterator(); Object[] toArray(); }
以上是Collection
接口的经常使用方法,由于3个子接口都继承了这个接口,所以在它们各自特有方法外,都会实现以上方法。安全
Java 中有四种常见的Map实现——HashMap, TreeMap, Hashtable和LinkedHashMap:session
说到Map接口的话你们也许在熟悉不过了。Map接口实现的是一组Key-Value的键值对的组合。 Map中的每一个成员方法由一个关键字(key)和一个值(value)构成。Map接口不直接继承于Collection接口(须要注意啦),由于它包装的是一组成对的“键-值”对象的集合,并且在Map接口的集合中也不能有重复的key出现,由于每一个键只能与一个成员元素相对应。多线程
另外,Set接口的底层是基于Map接口实现的。Set中存储的值,其实就是Map中的key,它们都是不容许重复的。
Map接口部分源码
public interface Map<K,V> { 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<V> values(); Set<K> keySet(); Set<Map.Entry<K, V>> entrySet(); interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); } }
源码中的方法你们也比较熟悉。在遍历Map时,咱们能够经过keySet()
方法获取到全部key,它返回的是一个Set对象,遍历Set,经过key获取value。也能够经过entrySet()
的方式获取Entry的Set,遍历Set,经过Entry的getValue()
和getKey()
方法获取值和对象,这些在后面会详细讲到。
List接口对Collection进行了简单的扩充,所以它继承了Collection接口。其中大部分方法和其继承的Collection相同,至于不一样之处也不太经常使用,你们能够参考源码。
特色:
List中存储的元素是有序的,并且能够重复的存储相关元素。
特色:
ArrayList的底层使用数组实现,当元素的数量超过数组长度时,经过新建更大容量数组,将原数组内容拷贝一份,而后新增元素的方式存储元素。
优势:
相似数组的形式进行存储,所以它的随机访问速度极快。
缺点:
不适合于在线性表中间须要频繁进行插入和删除操做。由于每次插入和删除都须要移动数组中的元素。
能够这样理解ArrayList就是基于数组的一个线性表,只不过数组的长度能够动态改变而已。
对于ArrayList的详细使用信息以及建立的过程能够查看jdk中ArrayList的源码,这里不作过多的讲解。
对于使用ArrayList的开发者而言,下面几点内容必定要注意啦,尤为找工做面试的时候常常会被问到。做者去年面试的时候,都已经被问烦了
注意啦!!!!!!!!
一、关于扩容问题:
默认ArrayListde的默认构造函数ArrayList()
会构造一个长度为10的数组。
ArrayList(int initialCapacity)
构造函数会构造一个指定长度的数组。
添加元素时,若是超出了长度,则以每次旧长度的3/2倍增加。
例:
new ArrayList(20);
扩容几回?
答案: 0次,由于直接产生了一个长度20的数组
二、ArrayList是线程不安全的,在多线程的状况下不要使用。
若是必定在多线程使用List的,你可使用Vector,由于Vector和ArrayList基本一致,区别在于Vector中的绝大部分方法都使用了同步关键字修饰,这样在多线程的状况下不会出现并发错误哦,还有就是它们的扩容方案不一样,ArrayList是经过原始容量*3/2,而Vector是容许设置默认的增加长度,Vector的默认扩容方式为原来的2倍(能够经过构造函数设置,如设置为2,扩容后长度为旧长度+2)。
切记Vector是ArrayList的多线程的一个替代品。
三、ArrayList实现遍历的几种方法
ArrayList<String> 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<String> iterator = list.iterator(); while(iterator.hasNext()) { System.out.print(iterator.next()); } System.out.println();
特色:
LinkedList的底层用双向链表实现。另外建议阅读其源码,并不难,会让你有醍醐灌顶的感受。
优势:
链表相对于实现ArrayList的数组来讲,其存储空间是散列的而不是连续的,所以在链表中间插入和删除元素时,无需移动后面的元素,只须要改变3个节点的关联便可。
缺点:
由于LinkedList不是空间连续的,所以随机读取时,须要从头至尾的读取,所以不如ArrayList来得快。另外,在使用双向链表实现时,须要额外提供空间供记录前驱节点和后继节点的地址,消耗了额外空间。
对于使用LinkedList而言,下面几点内容必定要注意啦
注意啦!!!!!!!!
一、LinkedList和ArrayList的区别和联系
主要从底层实现、优缺点(随机读取、新增、删除)等方面总结,详见以前总结的特色、优缺点,再也不赘述。
二、LinkedList的内部实现
强烈建议你们去看看源码,其内部使用双链表实现,若是你实在不想看,下面的代码提供了其节点结构,和最经常使用的add()
和remove()
方法。
// 长度 transient int size = 0; // 节点的表示 private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } // 此段不是源码,仅供理解思想 private void add(Node<E> node, E data) { Node<E> newNode = new Node<E>(node.prev, data, node); newNode.prev.next = newNode; node.prev = newNode; size++; } // 此段不是源码,仅供理解思想 private E remove(Node<E> node) { node.next.prev = node.prev; node.prev.next = node.next; size--; return node.item; }
三、LinkedList不是线程安全的
注意LinkedList和ArrayList同样也不是线程安全的,若是在对线程下面访问能够本身重写LinkedList
而后在须要同步的方法上面加上同步关键字synchronized
四、LinkedList的遍历方法
同ArrayList
LinkedList<String> 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<String> iterator = list.iterator(); while(iterator.hasNext()) { System.out.print(iterator.next()); } System.out.println(); }
五、LinkedList能够被当作堆栈来使用
因为LinkedList实现了接口Dueue,因此LinkedList能够被当作堆栈来使用,这个你本身研究吧。
Vector和ArrayList不论在实现,仍是使用上,都大同小异。所以也就不细说,他们主要的不一样就是Vector是线程安全的,它在一些方法上加了synchronized
关键字。
划重点了!!!!!!!!
一、Arraylist与Vector的区别
参见 https://zhuanlan.zhihu.com/p/28241176
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是Set接口的最多见的实现类了。其最底层是经过Hash表(一个元素为链表的数组)实现的。另外,Hash表底层依赖的两个方法hashcode与equals方法,想存入HashSet的元素须要复写这两个方法。
为何说其最底层呢?请先看下这段源码:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { private transient HashMap<E,Object> 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使用和理解中容易出现的误区:
一、HashSet中存放null值
HashSet中时容许出入null值的,可是在HashSet中仅仅可以存入一个null值哦。
二、 HashSet中存储元素的位置是固定的
HashSet中存储的元素的是无序的,这个没什么好说的,可是因为HashSet底层是基于Hash算法实现的,使用了hashcode,因此HashSet中相应的元素的位置是固定的哦。
三、 遍历HashSet的几种方法
HashSet<String> hashSet = new HashSet<String>(); hashSet.add("hello,"); hashSet.add("hello,"); hashSet.add("world"); // 第一种遍历方式,使用foreach遍历 for (String str : hashSet) { System.out.print(str); } System.out.println(); // 第二种遍历方式,使用迭代器遍历 Iterator<String> iterator = hashSet.iterator(); while (iterator.hasNext()) { System.out.print(iterator.next()); }
LinkedHashSet不只是Set接口的子接口并且仍是上面HashSet接口的子接口,和HashSet由HashMap实现同样,LinkedHashSet的底部由LinkedHashMap实现。
Set<String> set = new HashSet<String>(); set.add("hello"); set.add("world"); for (String str : set) { System.out.print(str); } System.out.println(); set = new LinkedHashSet<String>(); set.add("hello"); set.add("world"); for (String str : set) { System.out.print(str); }
上面的程序输出结果以下:
可见,LinkedHashSet集合一样是根据元素的hashCode值来决定元素的存储位置,可是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
由于它底层由LinkedHashMap实现,因此更多细节参加LinkedHashMap。
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