【java基础】 11 集合

目录

11.1 Java集合框架概述

1 集合与数组

2 集合应用场景

3 集合体系

11.2 Collection接口方法

11.3 Iterator迭代器接口

1 介绍

2 Iterator接口方法

3 foreach遍历集合

11.4 Collection子接口之一:List接口

1 ArrayList、LinkedList和Vector的异同

2 源码分析

3 List常用方法

11.5 Collection子接口之一:Set接口

1 子类 

2 特性(以HashSet为例说明)

3 添加元素过程(以HashSet为例说明)

4 LinkedHashSet添加元素

5 TreeSet

11.6 Map接口

1 map结构理解

2 HashMap的底层实现原理

3 LinkedHsahMap

4 Map中定义的方法

5 TreeMap

6 properties

11.7 Collections工具类


11.1 Java集合框架概述

1 集合与数组

  • 集合、数组都是对多个数据进行存储操作的结构,简称为java容器。
    • 说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化存储(.txt, .jpg, 数据库等)。
  • 数组在存储多个数据方面的特点:
    • 一旦初始化后,长度就确定了
    • 一旦定义好,其元素类型就确定了,只能操作指定类型数据。
  • 数组在存储多个数据方面的缺点:
    • 一旦初始化后,数组长度不能改变。
    • 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。 同时无法直接获取存储元素的个数。
    • 数组存储的数据是有序的、可以重复的(对于无序、不可重复的需求无法满足)。---->存储数据的特点单一

2 集合应用场景

3 集合体系

  • Java 集合可分为 Collection 和 Map 两种体系
  • Collection接口: 单列数据,定义了存取 一组对象的方法的集合
    • List:元素 有序 、可重复的集合 --->动态“数组”
    • Set:元素 无序、不可重复的集合 --->类似高中数学讲的“集合”
  • Map接口: 双列数据,保存具有映射关系“ key-value对”的集合 --->y = f(x)
Collection接口继承树:
Map接口继承树:

11.2 Collection接口方法

  • 添加
    • add(Object obj)
    • addAll(Collection coll):将集合coll的元素添加到当前集合中
  • 获取有效元素的个数
    • int size()
  • 清空集合
    • void clear()
  • 是否是空集合
    • boolean isEmpty()
  • 判断当前集合是否包含某个元素
    • boolean contains(Object obj):是通过元素的equals方法来判断是否 是同一个对象(比较的是内容,不是地址,所以,向collection接口的实现类的对象中添加数据obj时,要求obj所在类重写equals方法)
    • boolean containsAll(Collection c):判断c中所有元素是不是都在当前集合中。也是调用元素的equals方法来比 较的。
  • 删除
    • boolean remove(Object obj) :通过元素的equals方法判断是否是 要删除的那个元素。只会删除找到的第一个元素
    • boolean removeAll(Collection coll):取当前集合的差集。在当前集合中,删除coll1中所有元素
  • 取两个集合的交集
    • boolean retainAll(Collection c):把交集的结果存在当前集合中,不 影响c
  • 集合是否相等
    • boolean equals(Object obj):元素和顺序都比较
  • 转成对象数组
    • 集合--->数组:Object[] toArray()
    • 数组--->集合:List<类型> list = Arrays.asList(new 封装类数组);
  • 获取集合对象的哈希值
    • hashCode()
  • 遍历
    • iterator():返回迭代器对象,用于集合遍历

11.3 Iterator迭代器接口

1 介绍

  • Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
  • GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。

2 Iterator接口方法

  • next():返回迭代器中下一个元素
  • hasNext():判断是否还有下一个元素
  • remove():迭代器定义了remove方法,可以在遍历的时候,删除集合中的元素。不同于集合中的remove方法
遍历集合:
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
    //next():①指针下移 ②返回下移以后集合位置上的元素
    System.out.println(iterator.next());
}
过程:
注:
  1. 在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且 下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
  2. 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合 的第一个元素之前。所以写成如下形式是错误的:
while(coll.iterator().hasNext()){
    System.out.println(coll.iterator().next());
}
迭代中删除:
while(iterator.hasNext()){
    if(iterator.next().equals(123))
        iterator.remove();
}
注:
  1. Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方 法,不是集合对象的remove方法。
  2. 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用remove都会报IllegalStateException。

3 foreach遍历集合

格式:
for(集合元素类型 局部变量:集合对象){
    //操作
}
foreach内部仍然调用了迭代器。
练习题:
String[] str1 = new String[]{"a","a","a"};
//普通for循环赋值
for(int i = 0; i < str1.length;i++){
    str1[i] = "b";
}
String[] str2 = new String[]{"a","a","a"};
//foreach形式赋值
for(String s : str2){
    s = "b";
}

//str1: b b b  ;  str2: a a a
//foreach形式使用了新的变量,赋值只是赋给了s,没有改变原数组

11.4 Collection子接口之一:List接口

JDK API中List接口的实现类常用的有:ArrayList、LinkedList和Vector。

1 ArrayList、LinkedList和Vector的异同

  • 相同:三个类都实现了List接口,存储数据的特点相同,均存储有序的、可重复的数据
  • 不同:
    • ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[]存储
    • LinkedList:底层使用双向链表存储,对于频繁插入删除操作,使用效率高。
    • Vector:List接口的古老实现类,很少使用。线程安全,效率低;底层使用Object[]存储
补充题目:请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层 是什么?扩容机制?Vector和ArrayList的最大区别?
  • ArrayList和LinkedList的异同
二者都线程不安全,相对线程安全的Vector,执行效率高。 此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于 随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增 和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
  • ArrayList和Vector的区别
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于 强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用 ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大 小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

2 源码分析

ArrayList:
JDK1.7:
//采用空参构造器
ArrayList list = new ArrayList(); //底层直接创建一个初始容量为10的Object[]数组elementData
list.add(123); //elementData[0] = new Integer(123);
...
list.add(11); //此时elementData数组容量不够,则扩容。默认时,扩容为原来的1.5倍,同时需要将原有的数组中数据复制到新数组中

//建议使用带参构造器,指定容量
ArrayList list = new ArrayList(int capacity);
JDK1.8:
一开始创建一个长度为0的数组,当第一次调用add 时再创建一个始容量为10的数组。类似于懒汉式,延迟数组创建,节省内存。
LinkedList:
LinkedList list = new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null
list.add(123); //将123封装到Node中,创建了Node对象

//Node的定义
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
Vector:
  • 底层创建了长度为10的数组
  • 默认扩容为原来数组长度的2倍

3 List常用方法

  • 增:void add(Object ele) 
  • 删:Object remove(int index) / Object remove(Object obj)
  • 改:Object set(int index, Object ele)
  • 查: Object get(int index)
  • 插: void add(int index, Object ele)
  • 长度:size()
  • 遍历:Iterator迭代器; 增强for循环; 普通for循环

11.5 Collection子接口之一:Set接口

1 子类 

  • Hashset:作为接口的主要实现类;线程不安全;可以存储null值
  • LinkedHashSet:作为Hashset的子类;遍历其内部数据时,可以按照添加顺序遍历。 对于频繁的遍历操作,效率更高。
  • TreeSet:可以按照添加对象指定属性进行排序。放入数据必须是同一个类的对象。
注:Set接口中没有额外定义新的方法,使用的都是Collection接口中声明过的方法。

2 特性以HashSet为例说明)

无序性: 不等于随机性,存储的数据在底层数组中并非按照数组索引顺序添加,而是根据数据的哈希值决定的。
不可重复性: 保证添加的元素,按照equals方法判断时不能返回true,即相同的元素只能添加一次

3 添加元素过程以HashSet为例说明)

当添加元素a时
  1. 首先调用元素a所在的类的hashcode()方法,计算元素的哈希值
  2. 此哈希值接着通过某种散列算法计算出在HashSet底层数组中存放的位置(即:索引位置)
  3. 判断此位置有无元素,若果没有,则元素a添加成功;若已经有元素(或以链表形式存在的多个元素),则比较元素a与已存在元素的哈希值
  4. 如果哈希值相同,则调用元素a所在类的equals方法,若方法返回true,则添加失败,否则添加成功。
存储方式:
对于数组中已经有元素的情况:元素a与已经存在指定索引位置上的数据以链表形式存储。
jdk7以前:元素a放到原来的数组中,指向原来的元素
jdk8:原来的元素在数组中,指向新添加的元素a
要求:
  1. 向Set中添加数据,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。
  2. 重写的equals()和hashCode(Object obj)方法尽可能保持一致。 即:“相等的对象必须具有相等的散列码”。
重写hashCode()方法的基本原则:
  1. 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
  2. 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
  3. 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
例题1:在List内去除重复数据
public List duplicateList(List list){
    HashSet set = new HashSet();
    set.addAll(list);
    return new ArrayList(set);
}
例题2:判断输出
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1);
System.out.println(set); //含有1002,"BB", 1001,"CC"两个元素
//HashSet是按照哈希值存储和查找的,p1内容变了,哈希值变了,一开始存储的位置不变。此时用新的p1查找,相应哈希值位置上并无元素,所以删除失败。
set.add(new Person(1001,"CC"));
System.out.println(set); //含有1002,"BB", 1001,"CC", 1001,"CC"三个元素
set.add(new Person(1001,"AA"));
System.out.println(set); //含有1002,"BB", 1001,"CC", 1001,"CC", 1001,"AA"四个元素

4 LinkedHashSet添加元素

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据在添加时的前一个数据和后一个数据。

5 TreeSet

  • 向TreeSet添加的数据必须是同一个类的对象。
  • 需要指定排序方式:自然排序或定制排序。(类似于java比较器的操作)
  • 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()方法。(当compare()只使用其中某些属性比较,则如果两个对象其他属性不同,也无法同时存入集合)
  • 存储时采用红黑树
  • 定制排序
Comparator com = new Comparator(){
    public int compare(Object o1, Object o2){
        //比较的方式
    }
}
TreeSet set = new TreeSet(com);

11.6 Map接口

HashMap:作为Map的主要实现类;线程不安全,效率高;可以存储null的key和value;
                    jdk7之前:数组+链表
                    jdk8之后:数组+链表+红黑树
  • LinkedHashMap:保证在遍历map时,可以按照添加的顺序遍历。(在原有的HashMap的底层结构基础上,添加了一对指针,指向添加时前一个和后一个元素) 对于频繁的遍历操作 ,执行效率高
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。底层使用红黑树
HashTable:作为Map的古老实现类;线程安全,效率低;不可以存储null的key和value
  • Properties:常用来处理配置文件。key和value都是String类型。

1 map结构理解

  • Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应 的类,须重写hashCode()和equals()方法
  • 常用String类作为Map的“键”
  • key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到 唯一的、确定的 value
  • values为无序,可重复
  • 存放时是以Entry = (key, value)存放的,key-value构成了一个Entry对象,Entry是无序,不可重复的

2 HashMap的底层实现原理

jdk7:
HashMap map = new HasgMap(); 
在实例化以后,底层创建了长度为16的一维数组Entry[] table,这个长度在哈希表中被称为容量 (Capacity),在这个数组中可以存放元素的位置我们称之为 “桶” (bucket),每个 bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
map.put(key1, value1);
...//多次执行put
map.put(key, value);
  • 首先,调用key所在的hashCode()计算key1哈希值,此哈希值经过某种算法以后,得到在Entry[]数组中的位置。
  • 如果此位置上数据为空,此时key value添加成功;
  • 如果不为空(有一个或多个数据,以链表形式存在),比较key和已经存在的一个或多个数据的哈希值:
  • 如果key1哈希值与已经存在的数据哈希值不相同,则添加成功;
  • 如果key1哈希值和已经存在的某一个数据相同,继续比较,调用key1所在类的equals()方法,
  • 如果返回false,添加成功,
  • 如果返回true,使用value替换相应的value值(修改)
扩容问题:当超出临界值,且要存放的位置非空时,进行扩容。默认扩容为原来的2倍,并将原有的数据复制过来
jdk8:
  1. new HashMap():底层没有创建一个长度为16的数组
  2. 底层的数组是:Node[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. 当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。(查找效率更高)
补充:HashMap源码中重要的常量
  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  • DEFAULT_LOAD_FACTOR:HashMap的默认加载因子,0.75
  • TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树,8
  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,54
  • threshold:扩容的临界值,=容量*填充因子 16*0.75 = 12
  • loadFactor:填充因子

3 LinkedHsahMap

LinkedHashMap 是 HashMap 的子类
在HashMap存储结构的基础上,使用了一对双向链表来记录添加 元素的顺序
与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代 顺序:迭代顺序与 Key-Value 对的插入顺序一致
 
HashMap中内部类Node:
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

LinkedHashMap中的内部类Entry:
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after; //能够记录添加元素的先后顺序
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

4 Map中定义的方法

  • 添加、删除、修改操作:
    • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
    • void putAll(Map m):将m中的所有key-value对存放到当前map中
    • Object remove(Object key):移除指定key的key-value对,并返回value
    • void clear():清空当前map中的所有数据(与map = null操作不同)
  • 元素查询的操作:
    • Object get(Object key):获取指定key对应的value
    • boolean containsKey(Object key):是否包含指定的key
    • boolean containsValue(Object value):是否包含指定的value
    • int size():返回map中key-value对的个数
    • boolean isEmpty():判断当前map是否为空
    • boolean equals(Object obj):判断当前map和参数对象obj是否相等
  • 元视图操作的方法:(遍历)
    • Set keySet():返回所有key构成的Set集合
    • Collection values():返回所有value构成的Collection集合
    • Set entrySet():返回所有key-value对构成的Set集合
public void test1(){
    Map map = new HashMap();
    map.put("AA",123);
    map.put("45",45);
    map.put("bb",123);

    //遍历所有的key
    Set set = map.keySet();
    Iterator iterator = set.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }

    //遍历所有values
    Collection values = map.values();
    for(Object obj : values){
        System.out.println(obj);
    }

    //遍历key-value
    Set entrySet = map.entrySet();
    iterator = entrySet.iterator();
    while (iterator.hasNext()){
        Map.Entry entry = (Map.Entry) iterator.next();
        System.out.println(entry.getKey() + "---" + entry.getValue());
    }
}
注:遍历的顺序均是按照key的排序

5 TreeMap

  • 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
  • 具体操作与TreeSet类似

6 properties

Properties 类是 Hashtable 的子类,该对象用于处理属性文件。
Hashtable是个古老的 Map 实现类, Hashtable是线程安全的。 Hashtable实现原理和HashMap相同,功能相同。
public void test2() throws IOException {
    Properties properties = new Properties();
    properties.load(new FileInputStream("jdbc.properties"));
    String name = properties.getProperty("name");
    String pass = properties.getProperty("password");
    System.out.println(name + " " + pass);
}

11.7 Collections工具类

Collections:操作Collection、Map的工具类
常用方法:
  • 排序操作:(均为static方法)
    • reverse(List):反转 List 中元素的顺序
    • shuffle(List):对 List 集合元素进行随机排序
    • sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
    • sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
    • swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
  • 查找、替换
    • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
    • Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回 给定集合中的最大元素
    • Object min(Collection)
    • Object min(Collection,Comparator)
    • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
    • void copy(List dest,List src):将src中的内容复制到dest中
    • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
  • 同步控制
    • Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集 合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全 问题
List list1 = Collections.synchronizedList(list); //此时list1即为线程安全的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值