17章 容器深入研究
容器分类图:
Collections
- 数组有Arrays类填充,容器也有Collections类填充,这种工具类中一般都是静态方法不用创建它们的对象直接调用,所以很方便。
-
fill(list, T obj)
方法都只是复制一份对象的引用,并没有额外创建对象,并且只能填充List,它会将容器内的元素清空再添加元素。 nCopies(int n, T o) 返回一个List 功能和fill一模一样。
- addAll( list, T ... obj) 将元素添加到集合,集合本身也有addAll()方法并且还可以指定位置开始添加
2 Collection
- Collection中的方法在List和Set中都实现了,List还添加了额外的方法,如get(),这在Collection和Set中都没有,因为Set无序所以无法确定位置。
3 collection中的可选操作
- 可选的方法就是该方法在父类中会抛出异常,如果子类不需要该方法就不必重写它,一但调用则抛出异常,如果需要就去重写它的功能。
- Collection中的 各种添加 移除方法都是可选的。AbstractList ,AbstractSet,AbstractQueue中就是实现了可选功能,调用这些抽象类中的方法就会抛出异常。
List接口
List集合主要有两种具体的集合容器:ArrayList和LinkedList。
ArrayList:线程不安全,效率高。 底层实现是数组,查询块,修改删除慢。
LinkedList: 线程不安全,效率高。 底层实现是链表,查询慢,修改删除快。
Vector:线程安全,效率低。底层实现是数组。
(1).ArrayList:底层实现是数组,提供了根据数组下标快速随机访问的能力,但是增加和删除元素时因为需要引动数组的元素,因此比较慢。
(2).LinkedList:底层实现是链表,链表访问元素时必须从链表头至链表尾挨个查找,因此只能顺序访问,速度比随机访问要慢。但是增加和删除元素时,只需要修改链表的指针而不需要移动元素,因此速度比较快。
LinkedList
LinkedList除了实现了基本的List接口以外,还提供了一些特定的方法,使得LinkedList可以方便地实现Stack、Queue以及双端Queue的功能。
LinkedList提供的非List接口方法:
(1).getFirst():获取并且不移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(2).element():获取并且不移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(3).peek():获取并且不移除LinkedList集合中第一个元素。如果集合为空,则返回null。
(4).removeFirst():获取并且移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(5).remove():获取并且移除LinkedList集合中第一个元素。如果集合为空,抛出NoSuchElementException异常。
(6).poll():获取并且移除LinkedList集合中第一个元素。如果集合为空,则返回null。
(7).addFirst():向LinkedList集合的头部插入一个元素。
(8).add():向LinkedList集合的尾部插入一个元素。
(9).offer():向LinkedList集合的尾部插入一个元素。
(10).removeLast():获取并且移除LinkedList集合中最后一个元素。如果集合为空,抛出NoSuchElementException异常。
***********************************************************************************
Map接口
实现Map接口的类用来存储键值对(key-value)。Map接口的实现类有HashMap和TreeMap等。Map类中存储的键值对通过键来标识,所以键不能重复。
HashMap 效率高 线程不安全
Hashtable 效率低 线程安全
两者的用法都是一样的
Map遍历3种方法
Map<String, Object>map = new HashMap<String, Object>();
map.put(“test1”, object1);
……
map.put(“testn” , objectn);
(1).Map的values()方法可以获取Map值的集合:
- Iterator it = map.values().iterator();
- while(it.hasNext()){
- Object obj = it.next();
- }
(2).Map的keySet方法可以获取Map键的Set集合:
- Set<String> keys = map.keySet();
- for(Iterator it = key.iterator(); it.hasNext(); ){
- String key = it.next();
- Object obj = map.get(key);
- }
(3).通过使用Entry来得到Map的key和value:
- Set<Map.Entry<String, Object>> entrySet = map.entrySet();
- for(Iterator <Map.Entry<String, Object>> it = entrySet.iterator(); it.hasNext(); ){
- Map.Entry<String, Object> entry = it.next();
- String key = entry.getKey();
- Object value = entry.getValue();
- }
Set接口
Set接口是Collection接口的子接口,Set接口没有提供额外的方法,Set接口的特性是容器类中的元素是没有顺序的,而且不可以重复。Set容器可以与数学中”集合”的概念相对应。JDK API中所提供的Set容器类有HashSet,TreeSet等。HashSet的底层是HashMap实现的。
Hashset是java中一个非常重要的集合类,Hashset中不能有重复的元素,当一个元素添加到集合中的时候,Hashset判断元素是否重复的依据是这样的:
1)判断两个对象的hashCode是否相等
如果不相等,认为两个对象也不相等,完毕
如果相等,转入2)
(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的)
2)判断两个对象用equals运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。
Iterator迭代器
所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现了Iterator接口的对象。Iterator迭代器在java集合容器中应用比较广泛,对于List类型的集合,可以通过下标索引值获取到指定的元素,而对于Set类型的集合,因为Set是没有索引的,因此只能通过迭代器来遍历。
Iterator迭代器是一个顺序选择和遍历集合元素的对象,使用者不需要关心其底层的数据结构和实现方式。Java中的Iterator迭代器是单向的。
Iterator的常用方法如下:
(1).collection对象.iterator()方法:将集合对象转换为Iterator迭代器。
(2).iterator对象.hasNext()方法:判断迭代器中是否还有元素。
(3).iterator对象.next()方法:获取迭代器中下一个元素。
(4).iterator对象.remove()方法:删除迭代器中当前元素。
注意:使用迭代器的好处是,当数据结构从List变为Set之后,迭代集合的相关代码一点都不用改变。
ListIterator:
ListIterator是Iterator的子类,它只能有List类型的集合产生,ListIterator是一个双向的迭代器,即它可以向前和向后双向遍历集合。ListIterator的常用方法如下:
(1).list类型对象.listIterator():将List类型的集合转换为ListIterator迭代器。
(2).list类型对象.listIterator(int n):将List类型的集合转换为ListIterator迭代器,同时指定迭代器的起始元素为第n个元素。
(3).listIterator对象.hasNext():判断迭代器中是否还有下一个元素。
(4).listIterator对象.next():获取迭代器中的下一个元素。
(5).listIterator对象.hasPrevious():判断迭代器中是否还有前一个元素。
(6).listIterator对象.previous():获取迭代器中的前一个元素。
(7).listIterator对象.set(元素对象):将当前迭代到的元素设置为另一个值。
9.散列与散列码
HashMap使用hashCode() 和equals() 方法来找到值。hashCode方法产生散列码,如果不重写hashCode()和equals(),则用Object类的hashCode和equals()方法。hashCode通过产生散列码定位桶的位置,而equlas比较值是否相同。
equals方法必须满足5个条件:
- 自反性。x.equals(x) 返回true;
- 对称性。x.equals(y) 与 y.equals(x)结果相同
- 传递性。
- 一致性。
- 对任何不是null的
x
,x.equals(null)
一定返回false
。
散列的价值在于速度:散列使得查询得以快速进行。它将键保存在某处,以便能够很快找到。用数组的下标来保存散列的值,称为散列码,而数组存的是有相同的散列码的值的arrayList集合对象的地址。
import java.util.*;
public class SimpleHashMap<K, V> extends AbstractMap<K, V> {
// Choose a prime number for the hash table size, to achieve a uniform distribution:
static final int SIZE = 997;
// You can't have a physical array of generics, but you can upcast to one:
@SuppressWarnings("unchecked")
LinkedList<MapEntry<K,V>>[] buckets = new LinkedList[SIZE];
@Override
public V put(K key, V value){
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null){
buckets[index] = new LinkedList<MapEntry<K,V>>();
}
LinkedList<MapEntry<K,V>> bucket = buckets[index];
MapEntry<K,V> pair = new MapEntry<K,V>(key, value);
boolean found = false;
V oldValue = null;
ListIterator<MapEntry<K,V>> it = bucket.listIterator();
while(it.hasNext()){
MapEntry<K,V> iPair = it.next();
if(iPair.equals(key)){
oldValue = iPair.getValue();
it.set(pair); // Replace old with new
found = true;
break;
}
}
if(!found){
buckets[index].add(pair);
}
return oldValue;
}
@Override
public V get(Object key){
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null) return null;
for(MapEntry<K,V> iPair : buckets[index]){
if(iPair.getKey().equals(key)){
return iPair.getValue();
}
}
return null;
}
@Override
public Set<Map.Entry<K,V>> entrySet(){
Set<Map.Entry<K,V>> set = new HashSet<Map.Entry<K, V>>();
for(LinkedList<MapEntry<K,V>> bucket : buckets){
if(bucket == null) continue;
for(MapEntry<K,V> mpair : bucket){
set.add(mpair);
}
}
return set;
}
public static void main(String[] args){
SimpleHashMap<String, String> m = new SimpleHashMap<String, String>();
for(String s : "hi hello world how are you are you ok".split(" ")){
m.put(s, s);
System.out.println(m);
}
System.out.println(m);
System.out.println(m.get("be"));
System.out.println(m.entrySet());
}
}
查询一个值的过程首先就是计算散列码,然后使用散列码查询数组,如果没有冲突,则是一个完美的散列函数,通常冲突由外部链接处理,数组并不保存值,而是保存值的list。然后对list中的值使用equals方法进行线性查询,也可以优化。HashMap使用了调优,详情看源码解析。
hashCode想要实用,必须计算速度够快,并且必须有意义,必须基于对象的内容生成散列码,散列码不是独一无二的。但是通过hashcode和equlas 必须确定唯一的对象。好的hashcode应该能产生分布均匀的散列码。
hashCode()的计算方式:
- 给
int
变量result
赋予一个非零值常量,如17
- 为对象内每个有意义的域
f
(即每个可以做equals()
操作的域)计算出一个int
散列码c
:
域类型 | 计算 |
---|---|
boolean | c=(f?0:1) |
byte、char、short或int | c=(int)f |
long | c=(int)(f^(f>>>32)) |
float | c=Float.floatToIntBits(f); |
double | long l = Double.doubleToLongBits(f); |
Object,其equals()调用这个域的equals() | c=f.hashCode() |
数组 | 对每个元素应用上述规则 |
3. 合并计算散列码:result = 37 * result + c;
4. 返回result。
5. 检查hashCode()
最后生成的结果,确保相同的对象有相同的散列码。
持有引用
- Java.lang.ref类库包含了一组类,这些类为垃圾回收提供了灵活性。
- 三个继承Reference抽象类的类:SoftReference, WeakReference, PhantomReference,如果某个对象只能通过这三个对象才可以获得,那么GC会对这个对象作出不同的回收。
- 这三个容器类用来保存对象的引用。
- 对象可获得:栈中有一个普通引用可以直接指向这个对象,或者通过不同的对象间接指向一个对象,那么这个对象就是可获得的或者可达的,可获得的对象是不能被回收的。
- 普通引用:也称强引用,没有被Reference包装的引用,通过普通引用可获得的对象不能被释放。
- 如果一个对象被普通引用指向,那么他就不能被释放,一直占据内存,如果没有引用指向那么就会被回收,如果有个对象希望以后还能访问到但是也希望内存不足时可以回收那么对这类对象的引用就可以放在Reference里
- Reference对象的可获得性由强到弱,越强越不容易被回收:
- SoftReference 软引用 ,用来实现内存敏感的高速缓存,如果内存即将溢出时就回收对象。
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; sf.get();//有时候会返回null
- WeakReference 弱引用 用来“规范映射”而设计的,WeekHashMap中的key就是WeekReference。
- PhantomReference 虚引用 如果有个对象只有虚引用了那么他就会被回收。
- SoftReference 软引用 ,用来实现内存敏感的高速缓存,如果内存即将溢出时就回收对象。
- ReferenceQueue :GC时会在这种队列(可以自己创建,jvm也会自动创建)中查找虚引用,然后把虚引用的对象清理,softreference 和 weekreference 可以放也可以不放如ReferenceQueue中,但PhantomReference必须在ReferenceQueue中。
- WeakHashMap :key 保存弱引用,value 保存其他对象,当key弱引用指向的对象没有其他强引用引用那么key-value就会被回收。如果是普通HashMap那么key指向的对象除了多了一个HashMap的引用,还需要手动清理HashMap。
创建只读集合容器:
List,Set和Map类型的集合容器都可以通过下面的方法创建为只读,即只可以访问,不能添加,删除和修改。
- static Collection<String> data = new ArrayList<String>();
- data.add(“test”);
- static Map<String, String> m = new HashMap<String, String>();
- m.put(“key”, “value”);
(1).只读集合:
- Collection<String> c = Collections.unmodifiableCollection(new ArrayList<String>(data));
- System.out.println(c); //可以访问
- //c.add(“test2”);只读,不可添加
(2).只读List:
- List<String> list = Collections.unmodifiableList(new ArrayList<String>(data));
- System.out.println(list.get(0)); //可以访问
- //list.remove(0);只读,不可删除
(3).只读Set:
- Set<String> set = Collections.unmodifiableSet(new HashSet<String>(data));
- System.out.println(set.Iterator().next()) //可以访问
- //set.add(“test”);只读,不可添加
(4).只读Map:
- Map<String, String> map = Collections.unmodifiableMap(new HashMap<String, String>(m));
- System.out.println(map.get(“key”)); //可以访问
- //map.put(“key2”, “value2”);只读,不可添加
只读集合容器会在编译时检查操作,如果对只读集合容器进行增删等操作时,将会抛出UnSupportedOperationException异常。
只读集合容器类似于将集合对象访问控制修饰符设置为private,不同之处在于,其他类可以访问,只是不能修改。
线程同步集合容器:
Java集合容器中,Vector,HashTable等比较古老的集合容器是线程安全的,即处理了多线程同步问题。
而Java2之后对Vector和HashTable的替代类ArrayList,HashSet,HashMap等一些常用的集合容器都是非线程安全的,即没有进行多线程同步处理。
Java中可以通过以下方法方便地将非线程安全的集合容器进行多线程同步:
(1).线程同步集合:
Collection<String> c= Collections.synchronizedCollection(newArrayList<String>());
(2).线程同步List:
List<String> c= Collections.synchronizedList(newArrayList<String>());
(3).线程同步Set:
Set<String> c= Collections.synchronizedSet(newHashSet<String>());
(4).线程同步Map:
Map<String> c= Collections.synchronizedMap(newHashMap<String, String>());
推荐阅读
-
编程思想 之「容器深入研究」
-
Java编程思想: 容器深入研究
-
Thinking in java:17 容器深入研究
-
17章 容器深入研究
-
容器监控之 cadvisor
-
文字超出容器最大宽度实现从右向左匀速滚动
-
(20210316已解决)docker-compose创建postgresql容器exited with code 1
-
Docker使用之Tomcat容器环境设置 云计算集群DockerjavaTomcat
-
Spring MVC 原理探秘 - 容器的创建过程 博客分类: springmvc springmvc
-
写个web容器是不是很酷? 博客分类: Java Web java webweb 容器web服务器