1 泛型
-
数据类型的参数化,帮助建立类型安全;确保数据的安全性和免去强制类型转换;它供javac即编译使用, 将运行时异常(如类型转换异常)提升到编译期去发现;编译完毕擦除泛型相关类型
List list = new ArraryList(); Object obj = list.iterator().next(); // 不使用泛型,集合中元素取出为Object类型 String str = (String)obj; // 强制类型转换,可能ClassCastException List<String> list = new ArrayList<>(); // 使用泛型进行约束,如此集合元素添加时只能添加符合的;元素获取时类型也明确,更方便遍历
-
自定义泛型类:可参考ArrayList类去编写
-
< T>:用于限制参数类型即确保参数类型一致;具体类型在运行期决定,常用于类或方法上;< Object>由于类型范围太大,在写操作时常需要向上转型,读操作时向下转型,所以不做首选;配合集合如List< T>,集合可进行读写操作即能调用add()等方法
-
< ?>:任意参数类型即在不关心具体数据类型时使用通配符占位,常用于变量上;< ? extends T>上界通配符即入参需要是T类型或其子类型,< ? super T>下界通配符即入参需要是T类型或其父类型;配合集合如List< ?>,编译器无法确定数据类型,即集合是只读,不能增改,但可以清删
2 集合 / 容器
-
存储引用数据类型(基本数据类型装箱);可存储不同类型的数据
-
重写了toString() ,打印时输出[..., ...]
2.1 Collection
-
重写了equls()和hashCode(), 集合中元素用equals()比较,不用==严格判断
-
增:
-
boolean add(object):注意当参数是另一个集合时,添加变成元素+1,即[本来的元素列表,[okject]]
-
boolean addAll(collection):注意当参数是另一个集合时,是将该集合中所有元素添加,元素+n,即[本来的元素列表,新加集合的元素列表]
-
-
删:
-
void clear():集合中的所有元素的引用置为null;区别于集合=null,前者是集合中元素的引用对象被释放,后者是此集合被释放(当然如此一来元素也被释放)
-
boolean remove(object):移除
-
boolean removeAll(collection)
-
-
查:
-
boolean contains(obj) :是否包含元素
-
boolean containsAll(collection):是否包含集合
-
boolean isEmpty():集合中是否有元素;clear()操作之后返回true
-
int size():集合大小
-
-
改:
-
Object[] toArray():集合转为数组
-
boolean retainAll(collection) :保留,对当前集合只保留与指定集合中所有相同的元素;如果两个集合所有元素相同,返回flase
-
2.1.1 List
2.1.1.1 公有
-
有序:元素的取出时顺序与元素存入时的顺序一致;因为有序,对比Collection接口,方法多了索引参数index
-
元素可重复;允许多个null元素
-
增:
-
void ensureCapacity(int minCapacity):增加集合容量
-
void add(index, obj) / boolean addAll(index, collection)
-
-
删:
-
Object remove(index):返回被删的元素;失败会抛异常
-
-
查:
-
Object get(index):获取指定索引的元素
-
int indexOf / lastIndexOf(obj):获取元素的索引 / 逆向索引
-
ListIterator listIterator():获取所有元素;list集合特有的迭代器,同Iterator,用于集合的元素的增删查改
-
-
改:
-
void trimToSize():调整长度为元素个数,优化存储空间
-
Object set(index, element):指定索引处元素
-
List subList(fromIndex, toIndex):截取指定范围内元素为新集合
-
2.1.1.2 ArrayList
-
基于索引的数据结构,底层为动态数组,替代Vector
-
查询快:数组是连续存储单元,堆中存储实例化元素的地址和值,遍历地址所以速度快;下标遍历和随机访问如get(index)的时间复杂度O(1)
-
增删慢:需要重新计算大小和更新索引;不是尾插法时需要移动元素;超出容量做扩容时还要新建数组并复制数据;建议尾插法和指定初始容量,以防止动态扩充次数太多而影响性能;初始容量10,扩容为1.5倍
-
自定义实现ArrayList:
/** * 数组:连续存储单元,引用类型,堆中存储实例化元素的地址和值,遍历地址所以遍历快;固定容量 * int[] array = new int[10]/int[]{1,2,3}; * 动态数组ArrayList: */ public class MyArrayList<E> { private int size = 0; // 标记可写位置,每插入一个元素,size++;标记可读位置size-1 private static final int CAPACITY = 10; // public E[] elements; //java.lang.ArrayStoreException:将错误类型的对象存储到一个对象数组 public Object[] elements; public MyArrayList() { this(CAPACITY); } public MyArrayList(int capacity) { // this.elements = new E[capacity];这写法报错 this.elements = new Object[capacity]; } public void add(E element) { // this.elements[this.size++] = element; // 可以复用,即默认的添加是在最后一个元素之后,size = 0 this.add(this.size, element); } public void add(int index, E element) { if (index < 0 || this.size > this.elements.length) { throw new IndexOutOfBoundsException(); } this.addCapacity(); // 元素从后开始遍历,元素后移,腾出位置 for (int i = this.size; i > index; i--) { this.elements[i] = this.elements[i - 1]; } this.elements[index] = element; size++; } private void addCapacity() { if (this.size < this.elements.length) { return; } int newCapacity = this.elements.length + (this.elements.length >> 1); Object[] newElements = new Object[newCapacity]; for (int i = 0; i < size; i++) { newElements[i] = this.elements[i]; } this.elements = newElements; } public Object remove(int index) { check(index); Object removeElement = this.elements[index]; for (int i = index; i < this.size; i++) { this.elements[i] = this.elements[i + 1]; } size--; return removeElement; } public void set(int index, E element) { check(index); this.elements[index] = element; } public Object get(int index) { check(index); return this.elements[index]; } private void check(int index) { if (index < 0 || index >= this.size) { throw new IndexOutOfBoundsException(); } } /** * 元素是引用类型,把size设置为0让其不可用仅适用于基本类型 * 因为对象存储在堆上,清除时将地址置为null即释放地址指向的引用,才能被gc */ public void clean() { for (int i = 0; i < this.size; i++) { elements[i] = null; } this.size = 0; } @Override public String toString() { return "MyArrayList{" + "elements=" + Arrays.toString(elements) + '}'; } }
2.1.1.3 LinkedList
-
以元素列表形式存储/线性存储 数据,底层为(双向)链表:散列存储,对内存连续性要求不高,但每个节点与前后元素链接,节点多存储了指向前后元素的引用,所以更占内存
-
查询慢,即O(n):需要移动指针;遍历时用迭代器,不能用for(每一次get(i)都会遍历list)
-
增删快,O(1)
-
LinkedList针对首尾元素而专有的方法:
-
addFirst();addLast();添加元素,链表满时抛出异常
-
offerFirst();offerLast():(JDK1.6)添加元素,链表满时返回false
-
removeFirst();removeLast():删除元素;NoSuchElementException
-
poll();pollFirst();pollLast():获取元素后删除,(JDK1.6)如果链表为空,返回null
-
getFirst();getLast():获取首尾元素,如果链表为空,抛出NoSuchElementException
-
peekFirst();peekLast():获取首尾元素,(JDK1.6)如果链表为空,返回null
-
2.1.1.4 Vector
-
动态数组,线程同步,并发性低 ,古老,查询和增删都慢;初始容量10,扩容一倍
2.1.1.5 代码
// ArrayList测试
private static void testArrayList() {
List<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
// arrayList.add(3);
int size = arrayList.size();
for (int i = 0; i < size; i++) {
System.out.println(arrayList.get(i));
}
System.out.println(arrayList.contains("a")); // true
arrayList.remove("c"); // removeAll()
String[] array = arrayList.toArray(new String[]{});
}
// LinkedList测试
private static void testLinkedList(){
}
// Vector测试
private static void testVector(){
}
// HashSet测试
private static void testHashSet() {
Set<String> set = new HashSet<String>();
Set<String> set1 = new HashSet<String>();
Set set2 = new HashSet();
set.add("a");
set1.add("b");
set.addAll(set1);
set2.addAll(set);
Integer integer = 1;
set2.add(integer);
String[] strings = set.toArray(new String[]{});
for (String string:strings) {
System.out.println(string);
}
for (String str:set) {
System.out.println(str);
}
System.out.println(set2.size());
System.out.println(set2);
for(Object object: set2){
if (object instanceof Integer) {
int i = ((Integer) object).intValue();
System.out.println(i);
} else if (object instanceof String) {
System.out.println(object);
}
}
}
2.1.2 Set
2.1.2.1 公有
-
无序,遍历只能用迭代器
-
元素唯一:唯一性通过元素的hashCode()和equals()保证,add()时先调用这两个方法去判断;先hashCode()得到元素的hash值,等同于Map中的key,表示元素存放的地址,集合中不存在此元素的hash值,则直接添加;否则地址相同后哈希冲突,会再用equals()比较内容,为true表示元素相同,因此value值覆盖原来的元素(key不变);最多有一个null
-
Set存储自定义类,为了保证存储的对象唯一(如User中,同age,sex,name时认为是同一个对象,只存储一次),则需要重写hashCode()和equals();未重写前new对象后hash值明显不一致
@override public boolean equals(Object o) { if (this == o) return true; if (o == null || this.getClass() != o.getClass()) return false; User user = (User)o; return this.age == user.age && this.name.equals(user.name); } @override public int hashCode() { return Object.hash(age, name); }
-
增删,查找性能都高
-
哈希/散列表的原理:
-
对对象元素中的关键字(对象中的特有数据如fileld,为了提高效率,最好保证对象的关键字唯一),进行哈希算法运算,并得出具体的算法值(hash值,哈希码,散列码,int型整数)
-
hash值表示这个元素的位置(所以快速定位),是Object模拟的内存地址,通过hashCode()获取,可重写此方法返回值;存储hash值的结构即为hash表(元素用单向链表实现的散列表);这类似数组的索引,但hash值不是连续的
-
如果hash值出现冲突即相同,则需要再equals(),即判断这个关键字对应的对象是否相同。不同则重新散列(如key顺延+1)到其他位置
-
如此减少了equals()次数
-
能判断equals()且为true了,则hashCode()一定相同;如果equals()被覆盖,则hashCode()也必须覆盖
-
-
方法基本同父接口Collection
2.1.2.2 HashSet
-
底层基于HashMap实现;所以Map和Set通过迭代器可以相互转换
-
set.add(元素),该元素对应map的key,因此都是不重复的,value为每次new Object()
-
构造器中实例化的HashMap的加载因子都是0.75,容量16
2.1.2.3 LinkedHashSet
-
HashSet + 链表实现;链表用来维护元素的顺序;但元素依旧不能重复
2.1.2.4 TreeSet
-
底层为TreeMap
2.1.2.5 EnumSet
2.1.3 Queue
-
新集合;队列,FIFO即remove()方法移除最先add()的元素;队尾进/插入,队首出/删除;常用在生产者消费者模式
-
阻塞队列BlockingQueue:队列中为空时,获取元素操作将会阻塞;队列中元素已满时,插入操作将会阻塞;阻塞队列帮助控制何时阻塞线程,何时唤醒线程,甚至兼顾效率和线程安全
-
add()插入,remove()移除,element()检查:抛出IllegalStateException:Queue full或NoSuchElementException
-
特殊值:它们的方法不会报异常,而是返回插入成功与否的true和false,移除的元素或null
-
offer()插入,poll()移除,peek()检查;有重载的参数为时间的方法即超时阻塞,时间规定队列会阻塞线程一定时间,超时后线程退出
-
put()插入,take()移除:一直阻塞,即队列满时再调用插入,线程会一直等待队列中移除一个元素,然后插入;队列空时再调用移除,则线程一直等待队列插入一个能供移除的元素
-
2.1.3.1 ArrayBlockingQueue
- 基于数组结构的有界阻塞队列;默认有界为10
2.1.3.2 LinkedBlockingQueue
-
基于链表结构的阻塞队列,吞吐量高
2.1.3.3 SynchronousQueue
-
不存储元素的阻塞队列,即单个元素队列;插入操作必须等待其他线程将元素移除,否则一直处于阻塞状态
// 传统模式 class Data { private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() throw Exception { lock.lock(); try { // 等待,不生产 while (0 != number) { condition.await(); } // 生产 number++; condition.singalAll(); // 通知唤醒 } finally { lock.unlock(); } } public void decrement() throw Exception { lock.lock(); try { // 等待,不消费 while (0 == number) { condition.await(); } // 消费 number--; condition.singalAll(); // 通知唤醒 } finally { lock.unlock(); } } } main(String args) { Data data = new Data(); new Thread(() -> { for (int i = 0; i < 5; i++) { data.increment(); } }, "增线程").start(); new Thread(() -> { for (int i = 0; i < 5; i++) { data.decrement(); } }, "减线程").start(); } // 阻塞队列模式:等待和唤醒无需关注 class Data { private volatile boolean FLAG = true; // 默认进行生产和消费;设置可见,让生产和消费同时进行 private AtomicInteger atomicInteger = new AtomicInteger(); BlockingQueue<String> blockingQueue = null; public Data(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; } public void increment() throw Exception { String data = null; while (FLAG) { // 2S内添加数据 data = atomicInteger.incrementAndGet() + ""; returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS); } } public void decrement() throw Exception { String result = null; while (FLAG) { result = blockingQueue.poll(2L, TimeUnit.SECONDS); // 2S内没有获取到数据 if (StringUtils.isEmpty(result)) { FLAG = false; return; } } } } main(String args) { Data data = new Data(new ArrayBlockingQueue<>(10)); new Thread(() -> { data.increment(); }, "增线程").start(); new Thread(() -> { data.decrement(); }, "减线程").start(); }
2.2 Map
2.2.1 公有
-
key-value
-
增:
-
Object put(key, value):
-
添加时一般直接调用方法,不写返回值;返回值由key决定,key不存在,返回null,否则返回key对应的value,添加后新的value替换此value
-
当key为引用数据类型,且该引用对象没有重写equals(),则因其hashCode()不同,所以是两个key;如key都为new Person(1, "姓名"),打印map时可看到相同key的两个key-value对,但注意实际是不同的;区别于将其定义为一个变量person,做两次put(),实际后一次会覆盖前一次,毕竟此时key是同一个
-
-
void putAll(map):增加map
-
-
删:
-
void clear():清除元素引用
-
Object remove(key):移除key指定的元素;key存在,返回value,key不存在,返回null;一般直接调用,不写返回值,如果写了返回值,注意value类型是基本数据类型时,用包装类接收,否则报空指针
-
-
查:
-
Object get(key):键不存在,或者键有而value为null,则返回null;不能用此判断是否包含key,即以此获取不存在的key时返回null而不是抛出异常,再用null去业务处理如toString()时则空指针
-
boolean isEmpty():判断map是否为null
-
boolean containsKey(key):是否包含key
-
boolean containsValue(value):是否包含value
-
Collection values():将所有value作为Collection
-
-
Map中key和value的遍历以及与Set的互相转换:
// 获取Map中所有元素 Set keySet = map.keySet(); // 将map的所有key作为set for (Integer key: keySet) // 简单遍历,假设key为Integer类型 Iterator it = keySet.iterator(); // 迭代器遍历,返回set的迭代对象 // 如果确定集合中元素类型且类型统一,则Iterator<>较好,如Iterator<String> // 方法2:已知元素类型如String,则直接foreach遍历 for(String string: keySet) {} // 当存在多种类型时 for(Object obj: keySet) { if(obj instanceof …) {…} // 注意判断基本数据类型时是instanceof对应的包装类 else if(obj instanceof …) {…} } // 方法3:转为数组去遍历 String[] strings = keySet.toArray(new String[]{}); // 遍历迭代对象:while方式 while(it.hasNext()) { Object key = it.next(); Object value = map.get(key); } // for方式 for (Iterater<Object> keySet: it) {} Set entrySet = map.entrySet(); // 将key-value对作为set Iterator it = entrySet.iterator(); while(it.hasNext()) { Map.Entry me = (Map.Entry)it.next(); // Entry是Map内部类,即元素数组(key-value)是它的对象,持有指向下一个元素的引用 System.out.println(me.getKey()+"::::"+me.getValue()); } // ---------------------------------------------------------- // 遍历键值对 Set<Map.Entry<Integer,String>> entrySet = map.entrySet(); for (Map.Entry entry: entrySet) { System.out.println(entry.getKey() + "->" + entry.getValue()); } // 遍历键,通过键得到值 Set<Integer> keys = map.keySet(); for (Integer integer: keys) { String value = map.get(integer); System.out.println(integer + "=>" + value); } // 遍历值 Collection<String> values= map.values(); for (String value:values) { System.out.println(value); }
2.2.2 Hashtable
-
继承自Dictinary类;实现Map,Cloneable(可复制),Serializable三个接口
-
key不支持null
-
线程安全/同步,它的每个操作数据的方法,如remove(),get(),put()等都被做了同步控制
-
初始容量11;扩容2n + 1
-
不保证元素插入的次序:如map.put(1, "1");map.put(2, "2");不一定先插入1;但输出的时候一定和插入的时候顺序一致,如先插入的如果是1,则先输出的也是1
-
特有方法:elements();contains()
-
不推荐;保留的古老类,性能低;同步机制虽然避免了多线程同时操作,但在iterator遍历中的put,remove,clear等操作却会被多线程执行成功
2.2.3 HashMap
-
替代Hashtable;数组 + 链表;查询效率O(N)
-
JDK1.8优化:数组+链表+红黑树,即链表节点数预设值8:当链表节点数超过8,数组长度超过64时,链表转为红黑树,元素以内部类Node节点存储;否则链表深度太深,增删查找性能下降;节点数低于6时又转回链表;查询效率O(logn)
-
继承自AbstractMap类;实现Map,Cloneable,Serializable三个接口
-
key可以有一个为null,存在下标为0处
-
初始容量16;扩容2n;扩容时机为:超过容量乘以负载/散列因子(默认为0.75)时;加载因子表示hash表中元素填满的程度,太小则浪费空间,太大则增加了元素冲突几率,都会导致查询性能下降
-
容量设置为16即2的次幂:在取模/触发运算时比较耗时,此时可转为位运算,这也是HashMap比Hashtable效率高的原因之一
-
-
hashCode()计算key的hash值,值与map大小取余,得到元素在HashMap(hash表)中的存储(索引)位置;值已存在即该位置已经存在元素,则增加了链表结构;遍历链表中数据执行equals(),相同则覆盖(区别于HashSet,相同时插入操作不执行),都不同则该元素插入链表;优化则尽量让hash值分布均匀而不出现链表,省去遍历链表
-
自定义实现HashMap:
/** * 自定义HashMap:封装类成员: * key: * value: * next:链表指向下一节点,key重复时next不为null;双向链表则有prev * hash:hash值,key重复时,get操作还需要对比hash值 * 实现get()/put() */ public class MyMap<K, V> { // 数组数据结构,数据类型为定义的链表 private Entry<K, V>[] keyTable = null; // 容量 private int size = 0; // 默认容量 private static int defaultLength = 16; public MyMap() { this.keyTable = new Entry[defaultLength]; } // 实现map.put();注意返回类型为V,hashMap源码的put()也是返回的V public V put(K k, V v) { int index = hash(k); Entry<K, V> kvEntry = this.keyTable[index]; if (kvEntry == null) { this.keyTable[index] = new Entry<>(k, v, index, null); size++; } else { this.keyTable[index] = new Entry<>(k, v, index, kvEntry); } return this.keyTable[index].getV(); } // 通过hash计算key的位置:0 - 15 private int hash(K k) { int i = k.hashCode() % (defaultLength - 1); return Math.abs(i); } // 实现map.get() public V get(K k) { if (size == 0) { return null; } int index = hash(k); Entry<K, V> entry = getEntry(k, index); return entry == null ? null : entry.getV(); } private Entry<K, V> getEntry(K k, int index) { for (Entry<K, V> entry = this.keyTable[index]; entry != null ; entry = entry.next) { if (entry.hash == index && (k == entry.getK() || k.equals(entry.getK()))) { return entry; } } return null; } // 链表数据结构 class Entry<K, V> { K k; V v; int hash; Entry<K, V> next; public Entry(K k, V v, int hash, Entry<K, V> next) { this.k = k; this.v = v; this.hash = hash; this.next = next; } public K getK() { return k; } public void setK(K k) { this.k = k; } public V getV() { return v; } public void setV(V v) { this.v = v; } } }
2.2.4 ConcurrentHashMap
-
综合Hashtable和HashMap;底层基于数组+链表+红黑树;线程安全,通过CAS实现的乐观锁保证
-
二义性:get()方法返回null时,不清楚是因为key不存在而返回null,还是因为key存在但value值为null而返回null;HashMap用containsKey()方法避免了这个问题,但ConcurrentHashMap没有解决二义性问题,因为它常用在并发环境下,get()与containsKey()方法调用期间无法确保其他线程的操作使得元素被设置或删除了,所以使用ConcurrentHashMap则key和value都不能为null
2.2.5 LinkedHashMap
-
使用双向链表来维护元素次序;会记录元素插入/取出的顺序
2.2.6 TreeMap
-
红黑二叉树实现;key需要实现Comparable接口,输出顺序基于其重写的compareTo()
2.2.7 代码
Map<Integer,String> map = new HashMap();
map.put(0, String.valueOf(0));
map.put(1, "1");
System.out.println("size:" + map.size());
System.out.println(map.get(0));
// 遍历键值对
Set<Map.Entry<Integer,String>> entrySet = map.entrySet();
for (Map.Entry entry: entrySet) {
System.out.println(entry.getKey() + "->" + entry.getValue());
}
// 遍历键,通过键得到值
Set<Integer> keys = map.keySet();
for (Integer integer: keys) {
String value=map.get(integer);
System.out.println(integer + "=>" + value);
}
// 遍历值
Collection<String> values = map.values();
for (String value: values){
System.out.println(value);
}
System.out.println(map+"----------------"+map.size());
// HashMap和Hashtable可以互转;内容全部清空
map = new Hashtable<>();
map.put(1, "new 1");
System.out.println(map + "--------------" + map.size());
HashMap linkedHashMap = new LinkedHashMap(); // 子类使用双重链表维护元素顺序
// Collections工具类测试
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("ab");
list.add("ac");
Collections.reverse(list); // 反序
System.out.println(list);
Collections.shuffle(list); // 随机排序
System.out.println(list);
Collections.sort(list); // 排序
System.out.println(list);
Collections.swap(list, 0, 1); // 交换
System.out.println(list);
Collections.rotate(list, 2); // 旋转,参数2表示移动个数
System.out.println(list);
System.out.println(Collections.min(list));
List synchronizedList = Collections.synchronizedList(list); // 使其线程安全
2.3 List与Map
-
List与Map合作:存储表格数据:
-
表中每行存储为map:map1.put("id", id);map1.put("name", name);map2...
-
整个表格存储为list;即List< Map< String, Object>>
-
如果使用ORM思想:每一行为一个Bean,整个表存储为map或list
-
-
集合的遍历:
-
集合遍历时不允许修改集合,否则ConcurrentModificationException
-
可以使用迭代器删除元素
-
单线程:Iterator的remove();或者建立一个记录需要删除的元素的集合,循环完以后统一删除
-
多线程:使用并发集合类
-
-
3 Collections
-
集合工具类
-
增:
-
void fill(list, obj):填充集合元素
-
-
查:
-
Object min(collection)/max(collection):获取集合中最大、最小元素;前提是集合元素已实现可比较
-
int binarySearch(list, key):查找
-
-
改:
-
void sort(list, [comparator]):元素的自然顺序排序/按指定规则排序
-
void swap(list, i, j):交换集合中两个位置上的元素
-
void reverse(list):元素反序
-
void shuffle(list):元素随机排序;洗牌思想:总最后一张开始向前遍历,与随机一张牌交换顺序
-
synchronizedXxx():包装集合使其线程安全;Xxx表示各种集合,参数为集合对象
-
如ArrayList不是线程安全的,为了保证线程安全,手动用synchronized来同步所有调用其方法的地方;而工具类的synchronizedList()方法,底层实质也是在集合的每个方法上进行了synchronized同步且同一个监听器(Vector虽然线程安全,但效率低且不是同一个监听器,不能保证多个方法的组合是线程安全的)
-
如果是读多写少的场景,即高并发情况下,注重效率又保证安全,则使用JUC,即JDK提供了java.util.concurrent并发集合包;Copy On Write写时复制思想:拷贝原始数据,进行操作;如CopyOnWriteArrayList,ConrurrentHashMap,CopyOnWriteArraySet等;非此场景无需使用,复制的代价较大
// 源码 public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); // 加锁 try { Object[] elements = getArray(); // 集合元素转为数组 int len = elements.length; Object[] newElements = Arrays.cppyOf(elements, len + 1); // 写时,复制:复制数组为新数组 newElements[len] = e; // 元素添加到新数组末尾 setArray(newElements); // 数组转为集合 return true } finally { lock.unlock(); // 释放锁 } }
-
-
emptyXxx() / singletonXxx() / unmodifiableXxx():设置集合不可变,只读
-
rotate(list, distance):元素旋转,参数2表示移动个数
-
4 Iterator
-
迭代器:集合遍历
-
Iterator it = collection.iterator():接口,线程安全;容器都有迭代器对象;或foreach循环;List集合还可以根据索引使用普通for循环
-
List额外提供的方法:ListIterator listIt = list.listIterator();ListIterator(继承自Iterator)扩展的方法(向前迭代功能和添加元素):
-
boolean hasPrevious()
-
Object previous()
-
void add()
-
boolean hasNext()
-
Object next()
-
void remove()
-
-
多线程对同一集合操作时,使用快速失败(fail-fast)机制:遍历过程中只能以此方法操作;不能使用集合或其他线程的操作,否则ConcurrentModificationException
-
最佳实践:
-
不建议同一个集合中存储多种类型的数据,即最好使用泛型来限定类如List< String>
-
使用有限并发的集合类(易扩展)而非同步(线程安全)的
-
使用接口来代表和访问集合(如List存储ArrayList)
-
使用迭代器来循环集合
// Iterator迭代器测试 private static void testIterator(Collection<String> collection) { // foreach遍历 for (String string:collection) { System.out.println(string); } // Iterator遍历 Iterator<String> iterator = collection.iterator(); while (iterator.hasNext()) { String string = iterator.next(); System.out.println(string); } collection = new ArrayList<String>(); collection.add("A"); collection.add("B"); collection.add("C"); }
-