集合框架由来
Java2之前,Java是没有完整的集合框架的.它只有一些简单的可以自扩容的容器类,比如 Vector,Stack,Hashtable等
为什么存在容器(可以存储多个数据)
数组的弊端:
1,长度是不可变的,一旦数组初始化之后,长度是固定的
2,在N个地方需要存储多个数据,都得专门写数组的操作方法,代码和功能重复
3,数组定义方法参差不齐
集合框架:是为表示和操作集合而规定的一种统一的标准的体系结构.任何集合框架都包含三大块内容:对外的接口,接口的实现和对集合运算的算法
为什么需要集合框架:
1):提供功能的复用
2):专注于业务开发,而不是数据结构和算法
**************** Vector 类 ********************
底层是存储数据的,我们通过Vector对象的add方法来存储数据,其实底层依然是存储到 Object 数组中
默认初始容量是10
支持同步 synchronized ,线程安全
(集合只能存储对象,不能存储基本数据类型)(Java5之前,必须对基本数据手动装箱,Java5之后,自动装箱,其实底层依然是手动装箱(Java源文件))
*****集合中存储的对象,都存储的是对象的引用,而不是对象本身******
Vector v = new Vector(5); v.addElement(123); v.addElement(123); StringBuilder sb = new StringBuilder("ABC"); v.addElement(sb); System.out.println(v); //[123, 123, ABC] sb.append("SeeMyGo"); System.out.println(v); //[123, 123, ABCSeeMyGo] ------->(String改变的不是本身,不会变化)
****************************************************************
Vector 操作方法
===>增加操作
boolean add(E e) 将指定元素添加到此向量的末尾。
void add(int index, E element) 在此向量的指定位置插入指定的元素。
boolean addAll(Collection<? extends E> c) 将指定 Collection 中的所有元素添加到此向量的末尾,按照指定 collection 的迭代器所返回的顺序添加这些元素。
add 和 addAll 比较
Vector v = new Vector(5); v.addElement(123); v.addElement(123); Vector v2 = new Vector(); v2.add(1); v2.add(2); v2.add(3); v.add(v2); System.out.println(v); //[123, 123, [1, 2, 3]] v.addAll(v2); System.out.println(v); //[123, 123, 1, 2, 3]
===>删除操作
Object remove(int index) 移除此向量中指定位置的元素。
boolean remove(Object o) 移除此向量中指定元素的第一个匹配项,如果向量不包含该元素,则元素保持不变。
boolean removeAll(Collection<?> c) 从此向量中移除包含在指定 Collection 中的所有元素。
boolean retainAll(Collection<?> c) 在此向量中仅保留包含在指定 Collection 中的元素。
--->即求两个集合的交集
===>修改操作
Object set(int index, E element) 用指定的元素替换此向量中指定位置处的元素。
===>查询
Object get(int index) 返回向量中指定位置的元素。
Object[] toArray() 返回一个数组,包含此向量中以恰当顺序存放的所有元素。
---->返回的是数组的副本
源代码:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ protected Object[] elementData; public Vector() { this(10); } public synchronized void addElement(E obj) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; } ====>jdk1.0开始就有 public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } =======>jdk2.0开始归属集合框架 }
**************** 栈Stack ********************
存储特点:Last In First Out
底层可以使用 数组 或 链表 实现
----->>>>>>>源码: class Stack<E> extends Vector<E> 继承Vector类,数组方式
把数组的最后一个元素位置作为栈顶
方法:
Object peek() 查看堆栈顶部的对象,但不从堆栈中移除它。
Object pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象。
Object push(E item) 把项压入堆栈顶部。
boolean empty() 测试堆栈是否为空。
int search(Object o) 返回对象在堆栈中的位置,以 1 为基数。
>>>>>>Deque 接口及其实现提供了 LIFO 堆栈操作的更完整和更一致的 set,应该优先使用此 set,而非此类。例如:
Deque<Integer> stack = new ArrayDeque<Integer>();
Stack s1 = new Stack(); s1.push("c"); s1.push("a"); s1.push("b"); System.out.println(s1); //[c, a, b] System.out.println(s1.peek()); //b ArrayDeque s2 = new ArrayDeque(); s2.push("c"); s2.push("a"); s2.push("b"); System.out.println(s2); //[b, a, c] System.out.println(s2.peek()); //b
**************** ArrayList类 ********************
集合框架出现后用来替代 Vector的
同:底层原理都是基于数组的算法,一模一样
区别:
Vector : 所有方法都使用了 synchronized 修饰符, 线程安全,但是性能较低,适用于多线程环境
ArrayList : 所有的方法都没有使用 synchronized 修饰符,线程不安全,但是性能较高
---->即使以后在多线程环境下,我们也不使用Vector类
ArrayList list = Collections.synchronizedList(new ArrayList(...)); ... synchronized(list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
若方法需要返回一个 ArrayList 对象
但是该方法未找到对象,不会返回 null,会返回一个空集合
如 public Arraylist getAll(){
//return Collections.emptyList(); //最好的方式,返回空的列表
return new ArrayList();
}
在Java7之前,即使使用new ArrayList 创建对象,一个元素都不存储,但是在堆空间依然初始化了长度为10的Object数组
在Java7后,优化这个设计,new ArrayList 底层创建的是一个空数组
Object[] elementData = new Object[]{};
在第一次调用add()方法的时候,才会重新去初始化数组
**************** LinkedList类 ********************
LinkedList类是双向链表,单向队列,双向队列,栈的实现类
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList 线程不安全,多线程情况下:
List list = Collections.synchronizedList(new LinkedList(...));
在LinkedList中存在 Object get(int index) 根据索引位置获取对应的元素
链表本没有索引的概念,本不应该有索引,但是从Java2开始,存在了集合框架类作为List接口的实现类,
List中提供了根据索引查询元素的方法,LinkedList内部类提供了一个变量来当做索引
该方法少用,因为LinkedList不擅长查询操作,擅长保存和删除
另: LinkedList 要用 LinkedList来接收,否则其特有的方法不能使用
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Vector类 , ArrayList类,LinkedList类
面向接口编程
接口类型 变量 = new 实现类
List list = new ArrayList
共同特点:
1):允许元素重复(有序)
2):记录元素的先后添加顺序
区别:
Vector类:底层采用数组结构,方法都使用了 synchronized 修饰符,线程安全,但是性能相对于ArrayList低
ArrayList类:底层采用数组结构,方法没有使用 synchronized 修饰符,线程不安全,性能相对于Vector高
==>为保证ArrayList的线程安全,可使用 ArrayList list = Collections.synchronizedList(new ArrayList(...));
LinkedList类:底层采用双向链表结构,方法没有使用synchronized,线程不安全.
--->数组结构:插入和删除操作速度低,查询和更改快
链表结构:插入和删除操作速度快,查询和更改慢
使用选择:
Vector不用,优先使用ArrayList
集合元素的迭代遍历操作
Iterator:迭代器推荐使用 for 循环,性能更好一点
System.out.println("使用for循环操作iterator"); List list1 = new ArrayList(); list1.add("a"); list1.add("b"); list1.add("c"); for (Iterator it = list1.iterator();it.hasNext();){ System.out.println(it.next()); }
ListIterator :Iterator的子接口,可以双向迭代,但是注意指针开始在第一个位置,所以只能先往下迭代,然后再往上
Enumeration : 已被Iterator取代 ,方法同Iterator
for (Enumeration<E> e = v.elements(); e.hasMoreElements();)
System.out.println(e.nextElement());
深入分析for-each和迭代器
1):foreach操作数组,底层依然采用for循环+索引来获取元素
2):foreach操作Iterable的实例,底层其实采用Iterator
====>推荐使用foreach迭代数组和集合元素
例外情况:当需要边迭代边删除指定的元素时,此时只能使用迭代器的删除方法
迭代器循环时,在当前线程(main线程)A中,会单独创建一个新的线程B
A线程负责继续迭代,B线程负责去删除
B线程每次都会去检查和A线程中的元素是否个数相同,如果不是报 并发修改异常
解决方法:
不要使用集合对象的删除方法
在Collection接口中存在删除指定元素的方法 boolean remove(Object ele);
该方法只能从集合中删除元素,不能把迭代器中指定的元素删除
==>所以要使用 Iterator中的remove方法,
该方法会从两个线程中同时移除被删除的元素,保证了两个线程的同步
Iterator it = list.iterator(); while (it.hasNext()){ Object ele = it.next(); if ("b".equals(ele)){ it.remove(); } }
********泛型*******
底层依然使用的是 强转
泛型通配符: ? 不知道使用什么类型来接收的时候,用?表示未知. 此时只能接收数据,不能存数据
泛型的上限和下限:用来限定元素的类型必须是X类的子类,或父类,或相同
//泛型的上限:此时的泛型?,必须是Number类型或Number类的子类
private static void doWork1(List<? extends Number> list){
}
//泛型的下限:此时的泛型?,必须是Number类型或Number类的父类
private static void doWork2(List<? super Number> list){
}
泛型的擦除
1):泛型编译之后就消失了(泛型自动解除)
2):当把带有泛型的集合赋给了不带泛型的集合,此时泛型被解除(手动擦除)
//带有Integer类型的泛型
List<Integer> list = new ArrayList();
list.add(234);
//不带泛型的集合
List list2 = null;
list2 = list;//此时泛型被擦除
list2.add("abc");
//带有String类型的泛型
List<String> list3 = null;
list3 = list2;
String num = list3.get(0);
System.out.println(num);
//java.lang.Integer cannot be cast to java.lang.String
//等价于 String num = 123;报错
堆污染:
当一个方法即使用泛型的时候也使用可变参数,此时容易导致堆污染.
如:在Arrays类中的asList方法:public static<T> asList(T...a)
**************************** Set 接口 *********************************
Set只包含了从Collection继承的方法,不过Set无法记住添加的顺序,不允许包含重复的元素.
当试图添加两个相同元素进Set集合,添加操作失败,add()方法返回false
Set判断两个对象是否相等用equals,而不是用==.即两个对象equals比较返回true,Set集合是
不会接受这两个对象的
HashSet():底层采用哈希算法,实际上也是一个数组,提高查询速度
构造方法
HashSet() 构造一个新的空 set,其底层 HashMap 实例的默认初始容量是 16,加载因子是 0.75。
HashSet(Collection<? extends E> c) 构造一个包含指定 collection 中的元素的新 set。
HashSet(int initialCapacity) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。
HashSet(int initialCapacity, float loadFactor) 构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和指定的加载因子。
HashSet判断两个元素是否相等标准,二者缺一不可
1):两个对象的equals比较相等
2):两个对象的hashCode方法返回值相等
存储在哈希表中的对象,都应该覆盖equals方法和hashCode方法,并且保证equals相等的时候,hashCode也应该相等
当向HashSet集合中存入一个新的元素时,HashSet会先调用该对象的hashCode方法来得到该对象的hashCode值,然后决定
该对象在hashSet中的存储位置
如果需要把自定义的对象存储到哈希表中,该对象应该覆盖equals和hashcode方法
LinkedHashSet:具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现
(List和Set综合) HashSet子类
SortedSet 可排序的集合
NavigableSet 可范围查询的集合
===>实现类:TreeSet 红黑树查询
TreeSet集合底层采用红黑树算法,会对存储的元素默认使用自然排序(从小到大)
-->必须保证TreeSet存储的元素数据类型一致,否则运行 java.lang.ClassCastException 异常
构造方法:
TreeSet() 构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
TreeSet(Collection<? extends E> c) 构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。
TreeSet(Comparator<? super E> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。
TreeSet(SortedSet<E> s) 构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。
TreeSet的排序规则
自然排序
TreeSet调用集合元素的compareTo方法来比较元素的大小关系,然后将集合元素按照升序排列,要求TreeSet集合中元素得实现Java.util.Comparable接口
数据类型 排序规则
整形,浮点型 按数字大小顺序
Character 按字符集Unicode的值得数字大小排序
String 按字符串中字符的Unicode值排序
定制排序
在TreeSet构造器中传递Java.lang.Comparator对象,并覆盖public int Compare(Object o1, Object o2)再编写比较规则
1,定义一个比较器类 implements Comparator<>方法,编写比较规则
class PersonNameComparator implements Comparator<Person>{ //按名字长度排序 @Override public int compare(Person o1, Person o2) { if (o1.getName().length()>o2.getName().length()){ return 1; } else if (o1.getName().length()<o2.getName().length()){ return -1; } else { return 0; } }
2,Set<?> set = new TreeSet<>(new 比较器类());
Set<Person> set1 = new TreeSet<Person>(new PersonNameComparator());
====>比较两个对象是否相等
自然排序:compareTo方法返回0
定制排序:compare方法返回0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Set接口的实现类
共同的特点:
1):都不允许元素重复
2):都不是线程安全的类
解决方案:Set = Collections.synchronizedSet(Set对象);
区别:
HashSet:底层采用哈希表算法,查询效率较高
判断两个对象是否相等的规则:
1):equals比较相等
2):hashCode相同
要求:元素都要覆盖equals和hashCode方法
LinkedHashSet:
HashSet的子类,底层也采用哈希算法,但是也使用了链表算法来维持元素的先后添加顺序
判断两个对象是否相等的规则和HashSet相同
因为需要多一个链表来记录元素的顺序,所以性能相对于HashSet较低
一般少用,如果要求一个集合既要保证元素不重复,也要记录添加先后顺序,才选择使用LinkedHashSet
TreeSet:不保证元素的先后添加顺序,但是会对集合中的元素做排序操作
底层采用红黑树算法(树结构,比较擅长做范围查询)
TreeSet要么自然排序,要么定制排序
.......HashSet 等值查询效率高, TreeSet范围查询效率高(多用于数据做索引)
**************************** Map 接口 *********************************
严格上说,Map并不是集合,而是两个集合之间的映射关系(Map接口并没有继承Collection接口),然而Map可以存储
数据,所以还是习惯称之为集合
key集合(不允许重复--Set集合)
value集合(允许重复--List集合)
Entry: key-value(键值对)
Entry是多个键值对的集合
-->Set<Entry> --- Map
Set<Map.Entry<String, Object>> entry = map.entrySet(); for (Map.Entry<String, Object> entrys:entry){ String key = entrys.getKey(); Object value = entrys.getValue(); System.out.println(key+"--"+value); }
Set Map 算法
------------------------------------------
HashSet HashMap 哈希表
TreeSet TreeMap 红黑树
LinkedHashSet LinkedHashMap 哈希表/链表
等
-----------------------
Set底层是Map
把Set集合对象作为Map的key,再使用一个Object常量为value
Map的常用实现类:
HashMap:采用哈希表算法,此时Map中的key不会保证添加的先后顺序,key也不允许重复
key判断重复的标准是:key1和key2是否equals为TRUE,并且hashCode相等
TreeMap:采用红黑树算法,此时Map中的key会按照自然顺序或定制排序进行排序,key也不允许重复
key判断重复的标准是:compareTo/compare的返回值是否为0
LinkedHashMap:采用链表和哈希表算法,此时Map中的key会保证先后添加顺序,key不允许重复.
key判断重复的标准是:key1和key2是否equals为TRUE,并且hashCode相等
Hashtable:采用哈希表算法,是HashMap的前身
集合框架之前,表示映射关系就使用Hashtable
线程安全,但是性能较低
Properties:Hashtable的子类,此时要求key和value都是String类型
用来加载资源文件(properties文件)
---一般的,定义Map,key值用不可变的String类型
HashMap,TreeMap和LinkedHashMap都是线程不安全,但是性能较高
解决方案:
Map m = Collections.synchronizedMap(new HashMap(...));
Hashtable类线程安全,但是性能较低
List和Set及Map相互转换
List<Stng> list = new ArrayList();
把list转换为Set:
Set<String> set = new HashSet<>(list);//此时会消除重复的元素
把Set转换为List:
List<String> list2 = new ArrayList(set);
Map不能直接转换为List或Set
可以将Set,List以对象形式存入Map
*********************************************
集合操作工具类
1,Arrays类
在Collection接口中有一个方法 toArray 把集合转换为Object数组,
把集合转换为数组:Object[] arr = 集合对象.toArray();
数组也可以转换为集合(List集合)
//把数组转化为List对象
List<String> list = Arrays.asList("a","b","c");
>>通过Arrays.asList方法得到的List对象长度是固定的,不能增,也不能减
因为此ArraysList是 Arrays内部重写的内部类对象,而不是Java.util.ArrayList
>list.remove(0);
>此时报错:UnsupportedOperationException
2,Collections类
EMPTY_LIST 空的列表(不可变的)。
EMPTY_MAP 空的映射(不可变的)。
EMPTY_SET 空的 set(不可变的)。
获取空集合对象(没有元素的集合,注意集合不为null)
>返回同步安全的方法,当需要使用迭代的时候使用
List list = Collections.synchronizedList(new ArrayList());
...
synchronized(list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}