关系网:
接口Collection
Set两个分支:排序的(代表作TreeSet,LinkedHashSet)和无序的(HashSet)
按照被添加的顺序保存对象
,继承自HashSet,底层是LinkedHashMap,而LinkedHashMap也是继承自HashMap,并配合一个双端链表解决有序问题,所以Hash相关的操作其实都在HashMap里按照比较结果来升序保存对象,所以元素必须实现了Comparable
,底层是TreeMapQueue两个分支:LinkedList实现了一般化的队列接口,先进者先出,PriorityQueue基于Comparator等进行优先级排序,优先级高者先出
综合:
构造Collection:
Collection collection = new ArrayList<>(10); //参数是capacity,不是size
Collection collection2 = new ArrayList<>(); //参数默认值就是10
Collection collection3 = new ArrayList<>(collection); //类型是java.util.ArrayList
Collection collection4 = Arrays.asList(1,2,3,4,5);
Collection collection5 = Arrays.asList(1,2,3,4,5);
Collection collection6 = Arrays.asList(new Integer[]{5,6,7,8}); //类型是:java.util.Arrays$ArrayList,所以一般是将这个返回作为new ArrayList的参数
List list = new ArrayList(10);
List list = new LinkedList(); //没法带capacity参数,因为根本不用事先分配长度
Set set = new HashSet();
Set set = new TreeSet();
Set set = new LinkedHashSet();
Map map = new HashMap();
Map map = new TreeMap();
Map map = new LinkedHashMap();
长度:
size()
isEmpty()
clear()
capacity和size
添加:
add
addAll
查找:
contains
containsAll
indexOf
删除
remove(T),基于equals
remove(index)
removeAll(),基于equals
截取
subList(from, to_exclude),截取出来的list,肯定满足containsAll,并且不受sort和shuffle影响
排序,打乱
Collections.sort(List, Comparator)
Collection.shuffle()
交集
retainAll(list1, list2),取交集,依赖于equals方法
遍历
//Iterator只能前向移动
Iterator it = arraylist.iterator();
while(it.hasNext()){
Integer i = it.next();
System.out.println(i);
}
//ListIterator可以双向移动
ListIterator it2 = arraylist.listIterator();
while(it2.hasNext()){
Integer i = it2.next();
int prevIndex = it2.previousIndex();
Integer prev = it2.previous();
int nextIndex = it2.nextIndex();
Integer next = it2.next();
it2.hasPrevious();
it2.hasNext();
it2.remove();
System.out.println(i);
}
//遍历A
for(Integer i: arraylist){
System.out.println(i);
}
//遍历B
for(int i = 0; i < arraylist.size(); i++){
System.out.println(arraylist.get(i));
}
A:需要耗费一个迭代器 的开销,但获取元素的时间复杂度是O(1),速度快,但耗内存
B:不需要额外的空间,但get(i)时间复杂度是O(n),速度慢(除非是ArrayList,get(i)就是O(1)
所以:
ArrayList:推荐方式B
LinkedList:推荐方式A
提供了支持栈,队列,双端队列的方法
把这些方法分类列出来吧,好找
getFirst
remove
removeFirst
removeLast
peek()
poll()
offer()
element()
add
addLast
有个java.util.Stack,除了提供基本的:
push,peek,pop,empty等操作,还提供了随机访问
这里自己基于LinkdedList写一个,只提供栈的最小可用子集:
按照编程思想里所说的,java.util.Stack设计欠缺,基于LinkedList能产生更好的Stack,所以我们这个更好
public class Stack {
private LinkedList storage = new LinkedList<>();
public void push(T v){
storage.addFirst(v);
}
public T peek(){
return storage.getFirst();
}
public T pop(){
return storage.removeFirst();
}
public boolean empty(){
return storage.isEmpty();
}
public String toString(){
return storage.toString();
}
public static void main(String[] args) {
Stack stack = new Stack();
for(String s: "My dog has fileas".split(" ")){
stack.push(s);
}
while(!stack.empty()){
System.out.println(stack.pop() + " ");
}
}
}
set.contains(t)
set.containsAll(another set)
set.remove(t)
set.removeAl(another set)
可以用foreach遍历
import org.ayo.lang.JsonUtilsUseFast;
public class Dict {
public static class Entry{
public K k;
public V v;
public Entry(){
}
public Entry(K kk, V vv){
k = kk;
v = vv;
}
}
private Entry[] pairs; //键值对作为一个object[2]存储,所有键值对就是个
private int index;
public Dict(int length){
pairs = new Entry[length];
}
public void put(K key, V value){
if(index >= pairs.length){
throw new ArrayIndexOutOfBoundsException();
}
pairs[index++] = new Entry(key, value);
}
public V get(K key){
for(int i = 0; i < index; i++){
if(key.equals(pairs[i].k)){
return pairs[i].v;
}
}
return null;
}
public int size(){
return index;
}
@Override
public String toString() {
return JsonUtilsUseFast.toJson(pairs, true);
}
public static void main(String[] args) {
Dict map = new Dict(10);
map.put("1", "一");
map.put("2", "贰");
map.put("3", "叁");
map.put("4", "肆");
map.put("5", "伍");
System.out.println(map.size());
System.out.println(map.toString());
System.out.println(map.get("4"));
}
}
这里只做个说明,完全没考虑查找效率,长度扩展,键重复问题
散列
Dict中,get方法是在数组中遍历,线性查找,HashMap用的是散列码,哈希
一个可以作为key的类型定义,需要考虑hashCode()和equals()两个方法
参考hash.Groundhog这个类
先得考虑equals,然后equals相等的两个对象,hashCode()也应该相等
Map里会根据equals来确保键不重复
Object默认的equals比较的是对象地址
Object默认的hashCode也是基于对象地址
equals方法要满足的5个条件:
hashCode方法:
结论:
经验
public class Student{
public int id;
public String name;
public int hashCode(){
int result = 17;
result = 37*result + name.hashCode();
result = 37*result + id;
return result;
}
}
Queue接口:LinkedList实现了Queue接口,主要是offer,peek,poll,element
offer:插入队尾,失败返回false,好像还和capacity-restrick有关,这个不会改变capacity大小
add:插入队尾,但是可以改变capacity
element:拿到队头,但不remove,无则抛异常
remove:拿到队头,同时remove,无则抛异常 NoSuchElementException
peek:拿到队头,但不remove,无则返回null
poll:拿到队头,同时 remove,无则null
常用:
Queue queue = new LinkedList();
将LinkedList窄化为Queue接口
LinkedList支持双端队列的方法,但java里没有显式的定义这么个接口,因为不太常用,一般不会在两端都放元素,然后又需要从两端获取元素
自己定义:
public class Deque {
private LinkedList deque = new LinkedList();
public void addFirst(T e){
deque.addFirst(e);
}
public void addLast(T e){
deque.addLast(e);
}
public T getFirst(){
return deque.getFirst();
}
public T getLast(){
return deque.getLast();
}
public T removeFirst(){
return deque.removeFirst();
}
public T removeLast(){
return deque.removeLast();
}
public int size(){
return deque.size();
}
public String toString(){
return deque.toString();
}
}
所有Collection都可以foreach
Map怎么foreach才最合适??
for(Map.Entry entry: map.entrySet()){
entry.getKey(), entry.getValue();
}
//Empty系列:内部其实都对应一个private static final的实现类,无法插入数据,因为都是immutalble
List list = Collections.EMPTY_LIST;
Set set = Collections.EMPTY_SET;
Map map = Collections.EMPTY_MAP;
list = Collections.emptyList();
set = Collections.emptySet();
map = Collections.emptyMap();
Enumeration enumeration = Collections.emptyEnumeration();
Collections.emptyIterator();
Collections.emptyListIterator();
Collections.emptyNavigableMap();
Collections.emptyNavigableSet();
Collections.emptySortedMap();
Collections.emptySortedSet();
//unmodifiable系列:不可变集合
Collection c = new LinkedList();
Collection c1 = Collections.unmodifiableCollection(c);
list = Collections.unmodifiableList(List);
map = Collections.unmodifiableMap(Map);
NavigableMap nmap = Collections.unmodifiableNavigableMap(NavigableMap);
SortedMap smap = Collections.unmodifiableSortedMap(SortedMap);
set = Collections.unmodifiableSet(Set);
SortedSet sset = Collections.unmodifiableSortedSet(SortedSet);
NavigableSet nset = Collections.unmodifiableNavigableSet(NavigableSet);
//synchronized系列:性能没有CopyOnWrite和ConcurrentMap好
c1 = Collections.synchronizedCollection(c);
List list = Collections.synchronizedList(list);
map = Collections.synchronizedMap(Map);
SortedMap smap = Collections.synchronizedSortedMap(SortedMap);
NavigableMap nmap = Collections.synchronizedNavigableMap(NavigableMap);
set = Collections.synchronizedSet(Set);
SortedSet sset = Collections.synchronizedSortedSet(SortedSet);
NavigableSet nset = Collections.synchronizedNavigableSet(NavigableSet);
//singleton系列
Set set = Collections.singleton(T t);
List list = Collections.singletonList(T t);
Map map = Collections.singletonMap(key, value);
//checked系列,避免List list = new ArrayList(); list.add(1);这种不规范使用
Collections.checkedList(list, type);
排序
Collections.sort(list);
混排
Collections.shuffle(list)
反转
Collections.reverse(list)
用指定元素替换
Collections.fill(li,"aaa")
拷贝
如果目标list长度大,则剩余元素不会被覆盖
Collections.copy(list_src, list_dest)
最大最小
Collections.min(list)
Collections.max(list)
查找子列表: 这里
int locations = Collections.indexOfSubList(list, subList)
int locations = Collections.lastIndexOfSubList (list, subList);
根据指定的距离循环移动指定列表中的元素
Collections.rotate(list,-1);
{112, 111, 23, 456, 231 }按-1旋转之后是:111,23,456,231,112
java有三种类型:
基本数据类型8种
普通对象
数组对象
初始化时必须知道大小
Arrays里的操作,个人认为Arrays最重要的作用是提供了对数组的反射
//填充数组
Arrays.fill(array, 5);
//将数组的第2和第3个元素赋值为8
Arrays.fill(array, 2, 4, 8);
//对数组的第2个到第6个进行排序进行排序
Arrays.sort(array,2,7);
//比较数组元素是否相等
Arrays.equals(array1, array2)
//查找
Arrays.binarySearch(array1, 9)
Vector被废弃了,你应该使用CopyOnWriteArrayList
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
add时,会加锁,即一次就拷贝出一份,写完下一个写
read时,不用加锁,如果此时有人正写,读到的就是旧数据
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public E get(int index) {
return get(getArray(), index);
}
缺点:
具体参考:
和CopyOnWriteArrayList一个道理
HashTable被废弃了
现在应该使用ConcurrentHashMap和ConcurrentSkipListMap
ConcurrentHashMap是HashMap的线程安全版本,ConcurrentSkipListMap是TreeMap的线程安全版本
下面引自:http://www.infoq.com/cn/articles/ConcurrentHashMap
为什么HashMap线程不安全
因为多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap,如以下代码
final HashMap map = new HashMap(2);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
map.put(UUID.randomUUID().toString(), "");
}
}, "ftf" + i).start();
}
}
}, "ftf");
t.start();
t.join();
HashMap使用链表解决冲突,put时也会遍历这个链表,这个线程遍历着,那个线程
往里插入,可能造成死循环,个人理解
为什么HashTable不行了?
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
ConcurrentHashMap用的什么技术?锁分段
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
用法
V putIfAbsent(K key,V value)
如果不存在key对应的值,则将value以key加入Map,否则返回key对应的旧值
等价于:
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
boolean remove(Object key,Object value)
只有目前将键的条目映射到给定值时,才移除该键的条目。这等价于清单3 的操作
等价于:
if (map.containsKey(key) && map.get(key).equals(value)) {
map.remove(key);
return true;
}
return false;
boolean replace(K key,V oldValue,V newValue)
只有目前将键的条目映射到给定值时,才替换该键的条目
等价于:
if (map.containsKey(key) && map.get(key).equals(oldValue)) {
map.put(key, newValue);
return true;
}
return false;
V replace(K key,V value)
等价于:
if (map.containsKey(key)) {
return map.put(key, value);
}
return null;
2 我们也可以自己尝试实现一个CopyOnWriteMap
import java.util.Collection;
import java.util.Map;
import java.util.Set;
public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {
private volatile Map internalMap;
public CopyOnWriteMap() {
internalMap = new HashMap();
}
public V put(K key, V value) {
synchronized (this) {
Map newMap = new HashMap(internalMap);
V val = newMap.put(key, value);
internalMap = newMap;
return val;
}
}
public V get(Object key) {
return internalMap.get(key);
}
public void putAll(Map newData) {
synchronized (this) {
Map newMap = new HashMap(internalMap);
newMap.putAll(newData);
internalMap = newMap;
}
}
}
写时,加锁,并新建底层容器,然后重新指向这个新容器
读时,不加锁,如果正在写,读到的就是旧数据
懂了这个道理,实现CopyOnWriteLinkedList也就简单了
http://www.infoq.com/cn/articles/java-blocking-queue/
插入:
add(e): 抛出异常 IllegalStateException("Queue full")
offer(e): 不抛异常,返回false
put(e) : 阻塞
offer(e,time,unit):超时则退出
移除:
remove() 抛出异常 NoSuchElementException
poll() 不抛异常,返回null
take() 阻塞
poll(time,unit) 超时则退出
检查方法:
element() 抛出异常
peek() 不抛异常,返回特殊值