layout: post title: "java之集合" subtitle: " "java集合类"" date: 2018-10-03 06:00:00 author: "青乡" header-img: "img/post-bg-2015.jpg" catalog: true tags: - DataStructure&Algorithm
是什么
本质上是一个容器,用于放数据,存放数据的容器。
是一个数据结构,专门用于存放数据的。
内存里的数据结构,存放的是内存的数据。
作用
有2个,
1.往里面写数据
2.读里面的数据
最常用的ArrayList
2个主要操作,
1.写数据
首先看一下,我们平时最常用的往集合里写数据,而且一个接一个的写入数据,是按顺序的,即每次的数据都在写到集合的最后一个数据的后面,这样就不用移动任何数据,因为不是写到中间的位置,所以不用移动任何数据。
2.读数据
读的时候,是根据索引去读的,所有的集合数据结构当中,根据索引读数据的数据结构,速度是最快的。
底层原理
本质上是一个包装了各种数据类型的数组的类。
因为基于索引的数据结构,底层都是数组,封装了一下而已,所以数组索引是读最快的。
源码
如果需要插入数据和删除数据,从中间插入和删除,就使用链表集合LinkedList
优点
1.更新数据速度最快
方便插入和删除。因为不需要移动数据。只需要改变指针。
缺点 1.读数据很慢 因为索引是最快的,链表集合没有索引,需要遍历数据。虽然读的时候也是通过索引,但是本质上是遍历数据,因为链表结构只能从头结点移动指针指向索引指向的数据。
只能顺序查找。
源码
//ArrayList源码-读数据
E elementData(**int** index) {
**return** (E) elementData[index]; //虚拟机支持数组和数组索引这种数据库结构。
}
//LinkedList源码-读数据
/**
* Returns the (non-null) Node at the specified element index.
*/
Node<E> node(**int** index) {
// assert isElementIndex(index);
**if** (index < (size >> 1)) {
Node<E> x = first;
**for** (**int** i = 0; i < index; i++) //遍历数据
x = x.next; //作用是从第一个节点开始移动指针,直到到达索引指向的位置的节点
**return** x;
} **else** {
Node<E> x = last;
**for** (**int** i = size - 1; i > index; i--)
x = x.prev;
**return** x;
}
}
底层原理
双链表数据结构。
数据线程安全Vector
和数组集合一样。
唯一的不同,就是方法加了同步关键字,确保数据安全。
数据不能重复Set
不允许重复数据。
数据排序
List没有有排序的集合类。//List接口有sort()方法,所以实现类ArrayList和LinkedList都有实现sort()方法。
Set有,TreeSet extends SortedSet。//封装了TreeMap,本质上和TreeMap一样。 Map也有排序,TreeMap entends SortedMap。
源码实现?哪一种排序算法?
1.ArrayList
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) { //jdk早期就是使用归并排序这种算法,后来版本有优化
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) { //1.递归的结束条件:这里是还剩下几个数据的时候结束递归调用(剩下的几个数据使用插入排序算法进行排序),其实也可以在只剩下1个数据的时候结束递归调用(只剩下一个数据,所以是有序的,无需排序,直接return)
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
// Recursively sort halves of dest into src
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off); //2.一直递归调用方法自己
mergeSort(dest, src, mid, high, -off);
//3.归并排序算法
// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
2.TreeMap
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) { //插入第一个元素
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) { //传入比较器
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else { //默认比较器(key的默认比较器,比如String实现了自己的compare()方法 )
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
是否允许为null
List/Set/Map,都有是否允许为null的类。
Set和List的区别?
除了是否允许重复数据以外,还有其他区别吗?底层实现是一样的吗?
如果是一样,那为什么不是ArraySet,LinkedSet?同步类是哪一个?所以底层实现肯定是不一样的。看名字就不知道不一样。
Set各种实现类的区别?
1.排序集合TreeSet
确保数据有序。
怎么确保集合数据不重复?封装了TreeMap。和任何Map一样,TreeMap可以确保数据不重复,而且是数据有序。
怎么确保数据有序?封装了TreeMap。
TreeMap的实现原理也是通过比较,一个数据一个数据的比较。所以速度很慢。
如果不需要数据有序的要求,不要使用排序集合,因为操作数据的速度很慢。
2.HashSet
输入:键
输出:哈希值 //作用:作为HashMap的键的索引
算法:哈希函数
底层原理
使用了哈希函数。
具体来说,是使用了HashMap,封装了HashMap。
集合的元素就是键,值是一个常量数据。
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
//jdk源码-Hashtable
**private** **void** addEntry(**int** hash, K key, V value, **int** index) {
modCount++;
Entry<?,?> tab[] = table;
**if** (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = **new** Entry<>(hash, key, value, e);
count++;
}
使用哈希函数的好处是什么?或者说,封装HashMap的好处是什么?
HashMap键不能重复。所以,HashSet可以确保集合元素不重复。
HashMap为什么可以保证key不重复?看下面的代码。
//key相同会发生什么?1.相同key只插入一次 2.如果key相同,后面的value会覆盖全面的value
Map<String,String> m = new HashMap<String,String>();
m.put("a", "1");
m.put("b", "2");
m.put("a", "2");
System.out.println(m); //输出{a=2, b=2}
因为插入数据的时候,会判断,a[key的hash]是否有值,如果没有值,就插入;如果有值,就继续判断是否value相同,value不同覆盖前面的值,值相同就不插入。
//jdk源码
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
3.LinkedHashSet 与HashSet的区别?与HashSet的唯一区别就是,可以保证插入顺序。但是速度更快,因为要额外维护插入顺序。
实现原理就是双链表。类的名字反应了类的作用。
各自的应用场景?
怎么确保同步?
Collections.同步集合(集合类型);
任何一种不能同步的集合类(Collection/Set/List/Map),都可以通过这种方法得到一个同步的集合类。
具体的代码实现是,1.原始集合 2同步对象 ,1和2的组合,实现同步。
public static <T> Set<T> synchronizedSet(Set<T> s) { //得到一个同步集合类
return new SynchronizedSet<>(s);
}
/**
* @serial include
*/
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize //同步对象
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public int size() {
synchronized (mutex) {return c.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return c.isEmpty();}
}
public boolean contains(Object o) {
synchronized (mutex) {return c.contains(o);}
}
public Object[] toArray() {
synchronized (mutex) {return c.toArray();}
}
public <T> T[] toArray(T[] a) {
synchronized (mutex) {return c.toArray(a);}
}
public Iterator<E> iterator() {
return c.iterator(); // Must be manually synched by user!
}
public boolean add(E e) { //同步方法
synchronized (mutex) {return c.add(e);} //同步代码块
}
public boolean remove(Object o) {
synchronized (mutex) {return c.remove(o);}
}
是否插入顺序
1.List
ArrayList
是按索引,一个一个的插入,每次插入到最后。所以是插入顺序。
LinkedList 每次也是插入到最后。所以也是插入顺序。
总结
只要是每次插入到最后就是插入顺序。List都是插入顺序。
那什么情况不是插入顺序?用到了Hash就不是插入顺序。
最佳实践
1.数据重复List
数组ArrayList //读最快
双链表LinkedList //更新数据最快
插入数据有序 //所有集合类型都是插入有序的,只有映射才不是插入有序的,因为使用了哈希函数。
同步Vector //同步
数据有序TreeList //jdk没有。
1.为什么没有?
2.那怎么排序?
jdk Collections提供了排序方法。
static <T extends Comparable<? super T>> void sort(List<T> list)
Sorts the specified list into ascending order, according to the natural ordering of its elements.
static <T> void sort(List<T> list, Comparator<? super T> c)
Sorts the specified list according to the order induced by the specified comparator.
Collections类还提供了Set和Map的排序方法,虽然Set和Map都有自己的排序类TreetSet(底层也是封装的TreeMap)和TreeMap,但是TreeMap不同步。所以Collections类提供了同步排序方法。
static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
Returns a synchronized (thread-safe) sorted map backed by the specified sorted map.
static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
Returns a synchronized (thread-safe) sorted set backed by the specified sorted set.
2.数据不重复Set
1)共同点,就是首先要确保数据不重复
都封装了HashMap。映射可以确保键数据不重复。
2)类
HashSet //最基本的数据不重复
插入有序LinkedHashSet //1.数据不重复 2.确保插入顺序
数据有序TreeSet //1.数据不重复,实现原理是Map 2.数据有序,实现原理是TreeMap