11. Java集合

一、Java集合框架概述

  1. 集合、数组都是对多个数据进行存储操作的结构,简称Java容器。

说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt, .jpg, .avi,数据库中)

  1. 数组在存储多个数据方面的特点:

数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。比如:String[] arr;int[] arr1;Object[] arr2;

  1. 数组在存储多个数据方面的缺点:
    • 一旦初始化以后,其长度就不可修改。
    • 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
    • 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
    • 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

集合框架:

|----Collection接口:单列集合,用来存储一个一个的对象
    |----List接口:存储有序的、可重复的数据。  -->“动态”数组
        |----ArrayList、LinkedList、Vector

    |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
        |----HashSet、LinkedHashSet、TreeSet

|----Map接口:双列集合,用来存储一对(key - value)一对的数据   -->高中函数:y = f(x)
        |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

二、Collection接口方法

Collection coll = new ArrayList();
  1. add(Object e):将元素e添加到集合coll中
  2. int size():获取添加的元素的个数
  3. addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
  4. clear():清空集合元素
  5. boolean isEmpty():判断当前集合是否为空
  6. boolean contains(Object obj):判断当前集合中是否包含obj
    • 在判断时会调用obj对象所在类的equals()。
  7. boolean containsAll(Collection c):判断形参coll1中的所有元素是否都存在于当前集合中。
  8. boolean remove(Object obj):从当前集合中移除obj元素。
  9. boolean removeAll(Collection coll1):差集;从当前集合中移除coll1中所有的元素。
  10. boolean retainAll(Collection c):交集;获取当前集合和coll1集合的交集,并返回给当前集合
  11. boolean equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同。
  12. int hashCode():返回当前对象的哈希值
  13. toArray():集合 --->数组

拓展:

数组 --->集合:调用Arrays类的静态方法asList()

List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2

asList() 方法只能将引用类型数组转变为集合,基本数据类型使用的话,集合中存的是地址

注意:

  • Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是Vector 实例。Arrays.asList(…) 返回值是一个固定长度的 List 集合

三、Iterator迭代器接口

Collection接口继承了Iterator接口,Collection接口中定义了iterator()方法,返回迭代器对象,用于集合遍历

迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。

关于集合元素遍历的通用方式:

Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
Iterator iterator = coll.iterator();

一、使用迭代器Iterator接口

  1. 内部的方法:hasNext() 和 next()
  2. 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
  3. 内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
    //next():①指针下移 ②将下移以后集合位置上的元素返回
    System.out.println(iterator.next());
}

注意:

  1. 在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
  2. 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
  3. Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
  4. 如果还未调用next()或在上一次调用 next() 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
//    iterator.remove();IllegalStateException异常
    Object obj = iterator.next();
    if("Tom".equals(obj)){
        iterator.remove();
//        iterator.remove();IllegalStateException异常
    }
}

二、foreach循环

用于遍历集合、数组,jdk 5.0 新增

  1. 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
  2. 遍历集合的底层调用Iterator完成操作。

格式:

//遍历集合
//for(集合元素的类型 局部变量 : 集合对象)
for(Object obj:c){
    System.out.println(obj);
}
//遍历数组
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr){
    System.out.println(i);
}

四、Collection子接口一:List

List接口框架

|----Collection接口:单列集合,用来存储一个一个的对象
      |----List接口:存储有序的、可重复的数据。  -->“动态”数组,替换原有的数组
          |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
          |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储,线程不安全
          |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储

List接口方法

  • void add(int index, Object ele):在index位置插入ele元素
  • boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
  • Object get(int index):获取指定index位置的元素
  • int indexOf(Object obj):返回obj在集合中首次出现的位置
  • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
  • Object remove(int index):移除指定index位置的元素,并返回此元素
  • Object set(int index, Object ele):设置指定index位置的元素为ele
  • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合

总结:常用方法

增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()

遍历:
① Iterator迭代器方式
② 增强for循环
③ 普通的循环

ArrayList的源码分析:

jdk 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)

jdk 8

ArrayList list = new ArrayList(); //底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
...
后续的添加和扩容操作与jdk 7 无异。

小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

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;
    }
}

体现了LinkedList的双向链表的说法

新增方法:

  • void addFirst(Object obj)
  • void addLast(Object obj)
  • Object getFirst()
  • Object getLast()
  • Object removeFirst()
  • Object removeLast()

Vector的源码分析:

Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。

新增方法:

  • void addElement(Object obj)
  • void insertElementAt(Object obj,int index)
  • void setElementAt(Object obj,int index)
  • void removeElement(Object obj)
  • void removeAllElements()

五、Collection子接口二:Set

Set接口的框架:

|----Collection接口:单列集合,用来存储一个一个的对象
        |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
            |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
                |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
                                    对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
            |----TreeSet:可以按照添加对象的指定属性,进行排序。
  • Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
  • 向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
  • 重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
  • 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

Set:存储无序的、不可重复的数据

以HashSet为例说明:

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

添加元素的过程:

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:

  • 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
  • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
    • 如果hash值不相同,则元素a添加成功。--->情况2
    • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
      • equals()返回true,元素a添加失败
      • equals()返回false,则元素a添加成功。--->情况3

对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a

总结:HashSet底层数组+链表的结构。(Java7)

LinkedHashSet的使用

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

优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet

TreeSet

  1. 向TreeSet中添加的数据,要求是相同类的对象。
  2. 两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
  3. 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
  4. 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

六、Map接口

Map接口的框架:

 |----Map:双列数据,存储key-value对的数据       ---类似于高中的函数:y = f(x)
      |----HashMap:作为Map的主要实现类;线程不安全的,效率高;可以存储null的key和value
           |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
                            原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
                            对于频繁的遍历操作,此类执行效率高于HashMap。
     |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
                  底层使用红黑树
     |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
          |----Properties:常用来处理配置文件。key和value都是String类型

HashMap的底层:
数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)

Map结构的理解:

  • Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
  • Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
  • 一个键值对:key-value构成了一个Entry对象。
  • Map中的entry:无序的、不可重复的,使用Set存储所有的entry

HashMap的底层实现原理

HashMap map = new HashMap();在实例化以后,底层创建了长度是16的一维数组Entry[] table。
map.put(key1,value1);
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。

  1. 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1

  2. 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:

    • 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
    • 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
      • 如果equals()返回false:此时key1-value1添加成功。----情况3
      • 如果equals()返回true:使用value1替换value2。

补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

jdk8 相较于jdk7在底层实现方面的不同:

  1. new HashMap():底层没有创建一个长度为16的数组

  2. jdk 8底层的数组是:Node[],而非Entry[]

  3. 首次调用put()方法时,底层创建长度为16的数组

  4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。

    • 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    • 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

HashMap源码中的重要常量(专业术语)

  •  DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
    
  •  DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
    
  •  threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12,扩容为原来的两倍
    
  •  TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值(8),转化为红黑树
    
  •  MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
    

table:存储元素的数组,总是2的n次幂
entrySet:存储具体元素的集
size:HashMap中存储的键值对的数量
modCount:HashMap扩容和结构改变的次数。
loadFactor:填充因子
UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表
MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30

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中的所有数据

元素查询的操作:

  • 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集合
//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
System.out.println();
//遍历所有的value集:values()
Collection values = map.values();
for(Object obj : values){
    System.out.println(obj);
}
//遍历所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
    Object obj = iterator1.next();
    //entrySet集合中的元素都是entry
    Map.Entry entry = (Map.Entry) obj;
    System.out.println(entry.getKey() + "---->" + entry.getValue());
}
System.out.println();
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
    Object key = iterator2.next();
    Object value = map.get(key);
    System.out.println(key + "=====" + value);
}

总结:
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet() / values() / entrySet()

TreeMap类

  1. TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。
    TreeMap 可以保证所有的 Key-Value 对处于有序状态。
  2. TreeSet底层使用红黑树结构存储数据
  3. 两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
  4. TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。

Properties类

存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
涉及输入输出流

Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user = pros.getProperty("user");
System.out.println(user);

七、Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类
操作数组的工具类:Arrays

排序操作:(均为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 对象的所有旧值

关于void copy(List dest,List src)的使用

//报异常:IndexOutOfBoundsException("Source does not fit in dest")
//        List dest = new ArrayList();
//        Collections.copy(dest,list);
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);
System.out.println(dest);

Collections常用方法:同步控制

Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

示例:

//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);
posted @ 2023-09-30 16:21  LemonPuer  阅读(20)  评论(0)    收藏  举报