首页 > 代码库 > 集合框架学习笔记

集合框架学习笔记

集合框架由来
  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 = http://www.mamicode.com/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());
  }

集合框架学习笔记