一文搞定Java集合类,你还在为Java集合类而烦恼吗?

2021-02-19 21:19

阅读:865

标签:方式   前序遍历   扩容   arraylist   红黑树   超过   ++   面向接口   方法返回值   

导读:你还在为集合类而烦恼吗?别担心,我花了几天时间整理了一下集合类,文章通俗易懂,看完这篇文章保证让你茅塞顿开。内容很全,所以文章有点长,建议收藏再看。

 

文章目录

  • 1.什么是集合,要她作甚?
  • 2.集合存储的数据类型?
  • 3.常用的集合类
  • 4.Collection接口
    • Contains方法的进阶
  • 5.iterator方法
  • 6.List接口
    • ArrayList集合类
    • LinkedList集合类
    • Vector集合类
  • 7.Set接口的常用集合类
    • HashSet集合类
    • TreeSet集合类
  • 8.Map接口
    • HashMap集合类
    • Hashtable集合类
      • Properties集合类
    • SortedMap接口
      • TreeMap集合类
  • 9.集合涉及的知识扩展
    • 集合工具类Collections
    • 自平衡二叉树
    • 泛型
      • 自定义泛型
    • ForEach

 

1.什么是集合,要她作甚?

  • 集合实际上就是一个容器,数组其实就是一个集合,可以用来容纳其它类型的数据。集合在开发中使用较多。
  • 集合是一个容器,是一个载体,可以一次容纳多个对象。
  • 实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将每个数据展现出来。

2.集合存储的数据类型?

  • 集合不能直接存储基本数据类型,另外集合也不能直接接存储java对象。集合当中存储的都是java对象的内存地址。(即引用)
    那么:你可能会说平时我们遇到的 list.add(100);是怎么个情况?
    那是因为java的自动装箱,即将100自动打包装成Integer.
  • 集合在java本身是一个容器,是一个引用数据类型(对象)。
  • 在java中每一个不同的集合,底层会对应不同的数据结构。网不同的集合中存储元素,等于将数据放到了不同的数据结构中。
  • 那么什么是数据结构呢?
    数据结构是数据之间存在一种或多种特定关系的数据元素的集合。数据结构是一门课程,学过的就应该比较清楚。比如,数组,栈,队列,链表,二叉树,哈希表,图,网等。
  • 你使用不同得集合就等于使用了不同得数据结构。
  • 好消息是:java中已经将数据结构写好了常用得集合类,你只需要掌握如何使用。但是我还是建议你去学习一下数据结构的思想。毕竟这们课很重要。
  • new ArrayList();创建一个集合,底层是数组。

3.常用的集合类

  • java的集合类在 java.util.*;包下。
  • 最好数据集合的继承关系。
  • Java中集合分为两种类型。
    第一种:以单个元素存储。其超级父接口是:java.util.Collection;
    第二种:以键值对存储。(类似于python的集合)其超级父接口是:java.util.Map;
  • 所有的集合都继承Interable
  • 遍历即迭代
  • 下面是一些常用类和接口在java.util包中基本结构图:(希望能熟记)
    Collection集合关系结构图:
    灰色为线程安全得类,但现在用得比较少,因为有更好的方案。
    技术图片
    技术图片
    技术图片
  • 有序是指存进去和取出是相同的。
    技术图片
    Map集合关系结构图:
    技术图片
  • 总结
    技术图片
    技术图片
  • List集合的特点:
    有序可重复
    有序:存进去的顺序和取出来的顺序相同,每一个元素都有下标。
  • Set(Map)集合的特定:
    无序不可重复
    无序:存进去取出来的顺序不一定相同,另外Set集合中元素没有下标。不可重复,存进去1,不能再存储1了。
  • SortedSet(SortedMap)集合的特点:
    无需不可重复的。但是SortedSet集合中的元素是可排序的。
    无序:存进去的顺序和去除的顺序不一定相同,另外集合元素没有下标,不可重复。
    可排序:可以按照大小顺序排序。
  • Map集合的key,就是一个Set集合。
    在Set集合中放数据,实际上放到了Map集合的key部分。
    下面就一个一个的来说一说,以上的类和接口内容尽量熟记。

4.Collection接口

  • 关于Collection接口可以放的引用数据类型:(学习以下内容涉及的方法希望大家多练)
    如果没有使用泛型,只要是Object子类都可以存储。但是不能存基本数据类型哦,存储的只是对象的地址(唠叨一下)(泛型后期讲解,点点关注不失踪哦)
    回顾:接口不能直接new对象,要用类实现。
    关于Collection接口中常用的方法,其子类都可以用以下方法。
  • add()方法:向集合中添加元素。
  • addAll()方法:将指定的集合所以元素加到另一个集合中。
  • Size()方法:获取集合中的元素的个数。
  • Clear()方法:清空集合。
  • Contains(Object O)方法:判断当前集合是否包含元素O,返回boolean类型。
  • remove()方法:删除集合中的特定元素。
  • isEmpty()方法:判断集合是否为空,返回boolean类型。
  • toArray()方法:将集合转换成数组。
    练习:
import java.util.ArrayList;
import java.util.Collection;

public class CollectionTest1 {
    public static void main(String[] args) {
        //多态
        Collection W = new ArrayList();
//    自动装箱
        W.add(200);
        W.add(new Object());
//        自动装箱
        W.add(true);
//        获取集合的元素
        System.out.println(W.size());
//      清空集合
        W.clear();
        W.add("浩克");
        System.out.println(W.contains("浩克"));
//  删除集合中特定元素
        W.remove("浩克");
        System.out.println(W.isEmpty());
        W.add("皮卡丘");
        W.add("大熊猫");
        Object[] objs = W.toArray();
        for(int i=0 ;i.length;i++){
            Object O = objs[i];
            System.out.println(O);//自动调toString方法
        }
    }

}

Contains方法的进阶

Contains(Object O )方法的进阶(选读(如果看不懂建议去复习一下Object的equals方法),因为涉及equals方法得内容)。你可以猜测一下以下代码的执行结果。

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class ColletionTest2 {
    public static void main(String[] args) {

        Collection c = new ArrayList();//多态
        c.add("abc");
        c.add("100");
        c.add("def");
        c.add(100);
        c.add(new Object ());
//        遍历
        Iterator it = c.iterator();//获得迭代器
//        第二步:通过获得的迭代器遍历集合。
    while(it.hasNext()){
        Object obj = it.next();
        System.out.println(obj);
    }

    }
}

答案为:true,你答对了吗?ArrayList集合确实不包含x,下面来分析一下内存结构:
技术图片

  • Contains方法底层调用了equals方法来判断,是否为true,通过是否重写了equals方法来判断,如果重写了(string类sun公司重写了),如果重写了结果为true,当然你重写得重写对(你可以将String类换成自己写的类,并且不重写equals方法,你就会发现新大陆)哈哈,这里可能有点难懂,别放弃加油呀!弄它是。Integer包装类也重写了,java很多API都重写了equals方法,自定义类当然就没有,看你自己哦。
  • 如果你没搞懂comtains进阶原理,请记住以下结论。
    结论:放在集合里得元素得重写equals方法,如果没有重写比较的是内存地址,重写了比较的是内容,remove方法同理,也是跟comtains方法一样。
  • 以下验证一下关于re’move以上的结论。
import java.util.ArrayList;
import java.util.Collection;

public class CollectionTest4 {
    public static void main(String[] args) {
        Collection c =new ArrayList();
        String u1 = new String("abc");
        c.add(u1);
        String u2 = new String ("abc");
        c.remove(u2);
        System.out.println(c.size());
    }
}
  • 你会发现我明明没有删除u1怎么大小就变成了0呢?哈哈,好好品味一下吧。
  • 总结:我们自己创造类的时候养成重写toString和equals方法的习惯,不管是否用得着。

5.iterator方法

你可能感到奇怪这个方法为什么单独讲,这就不用我说了吧。因为很重要,所以加油呀。
Iterator为Iterable中的方法,被Collection接口继承。
解释:Collection接口以及子类调用父类Iterable方法:iterator();放回一个迭代器Iterator对象。(不要搞混哦,注意首字母的大小写,大小写不同,含义不同。)

  • 遍历集合/迭代集合,以下讲解的遍历/迭代方式,是所有Collection以及子类通用的一种方法,但是在Map集合中不能使用。
    迭代器的执行原理:

  • 迭代器 Iterator对象,的三个方法:

  • 迭代器最初并没有指向第一个元素。
    boolean hasNext()方法:如果还有元素可以迭代返回true.
    Object next();这个方法让迭代器往前进一位,并且返回指向的元素。

  • 两个方法配合实现迭代。

  • 第三个方法remove()后期聊。

  • 练习:

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class ColletionTest2 {
    public static void main(String[] args) {

        Collection c = new ArrayList();//多态
        c.add("abc");
        c.add("100");
        c.add("def");
        c.add(100);
        c.add(100);
        c.add(new Object ());
//        遍历
        Iterator it = c.iterator();//获得迭代器
//        第二步:通过获得的迭代器遍历集合。
    while(it.hasNext()){
        Object obj = it.next();
        System.out.println(obj);
    }

    }
}
  • 看以上代码结果你会发现ArrayList其结果是有序的,可重复。(啰嗦以下,加深印象,其它类型特点可以去练习一下,比如HashSet集合无序,不可重复。)注意存进去取出来都是原来的类型,只不过到控制台变成了字符串。
  • next()方法返回值类型必须为Object.
  • 生成迭代器,应该在最后获取。因为如果集合发生改变,迭代器没有重新获取而调用next()方法时,会出现ConcurrentMOdificotionException异常。
  • 因此集合发生改变时,迭代器应该重新获取。
  • 因此在迭代过程不能通过直接集合去删除元素(迭代器不知道,导致迭代器没有更新,会出异常)。但是我们可以用迭代器的第三个方法remove方法删除元素。
  • 练习:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionTest5 {
    public static void main(String[] args) {
        Collection c =new  ArrayList();
        c.add(15);
        c.add("fas");
        c.add(2222);
        Iterator it = c.iterator();
        while (it.hasNext()){
            Object o = it.next();
            it.remove();//用迭代对象的remove方法删除
            System.out.println(o);
        }
        System.out.println(c.size());
    }
}

6.List接口

  • List接口存储元素的特点:
    有序可重复。
    有下标。
    List接口特有的常用方法:
  • void add(int index , Object element)方法:指定下标添加元素,其后元素往后移,效率较低。而add方法默认在最后添加。
  • Object get(int index)方法:根据下标获取元素。
  • int indexOf(Object O)方法:获取对象第一次出现的索引(下标加一)。
  • int lastIndexOf(Object o)方法:获取对象最后一次出现的索引,没有的话返回-1。
  • Object remove(int index)方法:根据指定下标删除元素
  • Object set(int index , Object element)方法:修改指定下标的元素。
  • 对于list集合因为有下标所以可以采取下标遍历元素的方式。
  • 练习:
import java.util.*;

public class ListTest1 {
    public static void main(String[] args) {

        List myList  =  new LinkedList();
//        List myList2 =  new ArrayList();
//        List myList3 =  new Vector();
//        添加元素
        myList.add("cad");
        myList.add("dfsa");
        myList.add(6656);
        myList.add(6656);//默认在最后添加
        myList.add(1,"King");//在指定位置添加元素
        Iterator it  = myList.iterator();
        while (it.hasNext()){
            Object o = it.next();
            System.out.println(o);
        }
//        获取特定元素
        Object obj = myList.get(1);
        System.out.println(obj);
//        获取指定对象最后一次出现的索引
        System.out.println(myList.lastIndexOf(6656));
//        修改特定位置的元素
        myList.set(0,"fdafasdf");
        System.out.println(myList.get(0));
}
}

ArrayList集合类

  • ArrayList集合初始量为10(底层创建了一个长度为0的数组,当添加一个元素的时候,初始化容量为10),当元素大于10时,数组扩容原容量的1.5倍,尽量不要让其扩容,因为效率较低,所以初始化合理的容量很重要,底层为Object[]数组。
  • 新建ArrayList集合对象时,可以指定初始化容量。
    如:new ArrayList(15);(构造方法)
  • 数组检索效率高,但是随机增删效率较低,无法存储大数据量,因为很难找到连续的大存储空间。在末尾增删效率不受影响。
  • HashSet集合可以通过ArrayList的构造方法转换成ArrayList集合。
  • ArrayList集合是非线程安全的。
  • 易错点:关于集合中求大小,用size()方法,取元素用get(i)方法,不要用数组那一套,不管用。are you ok?

LinkedList集合类

  • LinkedList集合底层为双向链表(不懂链表的同学建议学习一下数据结构,我公众号【轻松玩编程】有清华教授的学习视频,关注即可领取)。
  • 下面照顾一下没学过链表的朋友,简单说一下链表。
  • 单项链表由存储的数据和下一节点的内存地址组成如图:。
    技术图片
    *缺点:单链表的存储地址不连续,插中间一个节点,需要从头节点开始查找,知道其上一个节点存储的地址才可。所以查找效率较低。
  • 优点:但是单链表增删效率高,因为增删不涉及大量元素的移动。
  • 双向链表是由存储的数据和该数据的上一节点和下一节点的内存地址组成如图:
    技术图片
  • ListedList的内部实现:
    技术图片
  • 最初这个链表没有任何元素。记住我们以后开发不需要关系是哪个集合,我们以后开发面向接口编程,调用的都是接口中的方法。

Vector集合类

  • Vector集合底层是一个数组。
  • 初始化容量也为10,扩容之后是原来容量的两倍,而ArrayList集合是原来的1.5倍。
  • Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。(效率较低,使用较少)
    那如何将非线程安全的转换成线程安全的呢?
  • 使用集合工具类。
    1.java.util.Collections;
    2.注意java.util.Collection 是集合接口。
    但是:java.util.Collection是集合工具类。
    练习:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class VectorTest1 {
    public static void main(String[] args) {
        List myList = new ArrayList();
//        变成线程安全的
        Collections.synchronizedList(myList);
        myList.add("444");
        System.out.println(myList.get(0));
    }
}

7.Set接口的常用集合类

  • 特点:无序不可重复。
  • Set接口继承Collection接口,并且Set接口并没有提供新的方法或常量。只是规定了她的实例不可包含任何重复的元素。所以我们这里就简单的说一下。

HashSet集合类

  • 无序不可重复,无下标。
  • 放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。

TreeSet集合类

  • 无序不可重复,但是存储的元素可以自动按照大小顺序排序。
  • 可排序集合。
  • 父类为SortedSet.
  • 练习:
import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest1 {
    public static void main(String[] args) {
        Set> aa = new TreeSet>();
        aa.add("a");
        aa.add("g");
        aa.add("c");
        aa.add("d");
        for(String cc : aa){//foreach,详情请看下文
            System.out.println(cc);
        }
    }
}

8.Map接口

  • Map和Collection没有继承关系。
  • Map集合以key和value的方式存储数据:键值对
  • key和value都是引用数据类型。
  • key和value都是存储对象的内存地址。
  • key起主导地位,value是key的附属品。
    == Map集合中常用的方法:==
  • V put (K key, V value)方法:向Map集合中添加键值对。
  • V get (Object key)方法:通过key获取value.
  • void clear() 方法:清空Map集合。
  • boolean containskey()方法:判断Map中是否包含是否包含key.
  • boolean containsValue()方法:判断Map中是否包含某个value.其内部调用的是equals方法。
  • boolean isEapty()方法:判断Map集合中元素个数是否为0。
  • Set keySet() 获取Map集合的所有的key.返回Set集合。
  • V remove(Object key)方法:通过key删除键值对。
  • int size()方法:获取
  • Collection values()方法:获取Map集合中所有的value,返回一个Collection
    练习:
public class MapTest2 {
    public static void main(String[] args) {
        //创建map集合对象
        Map,String>map = new HashMap>();
        //添加键值对
        map.put(1,"zhangshan");
        map.put(2,"daf");
        map.put(3,"fa");
        //通过key获取value
        String c = map.get(2);
        System.out.println(c);
        //获取键值对的数量
        System.out.println(map.size());
        //获取所有的value
        Collection> values = map.values();
        for(String s : values){
            System.out.println(s);
        }
        //通过key删除key-value
        map.remove(2);
        System.out.println(map.size());
        //判断是否包含某个key,value
        System.out.println(map.containsKey(5));
        System.out.println(map.containsValue("fa"));
        //清空键值对
        map.clear();
        System.out.println(map.size());

    }
}
  • Set>entrySet()方法:将Map集合转换成Set集合。
  • 练习:
package Day3;


import java.util.HashSet;
import java.util.Set;

public class MapTest1 {
//    声明静态内部类
    private static class InnerClass{
        public static void m1(){
            System.out.println("静态方法m1执行!");
        }
//        实例方法
    public void m2(){
        System.out.println("实例方法执行!");
    }
}
    public static void main(String[] args) {
        MapTest1.InnerClass.m1();
        MapTest1.InnerClass aa= new MapTest1.InnerClass();
        aa.m2();
        Set> set  = new HashSet>();
        Set.MyEntry,String >> set3 =new HashSet>();
    }
}
class MyMap{
    public static class MyEntry,v>{

    }

}

  • Map集合的遍历
    第一种方法:获取所有的key,通过遍历key,遍历value,通过key,找value.
    第二种方式:直接转换成Set集合。
    Set集合中元素的类型是:Map.Entry.
public class MapTest3 {
    public static  void main(String[] args) {
        Map,String> map = new HashMap();
        map.put(1,"sff");
        map.put(2,"fasdf");
        map.put(5,"fasdf");
        map.put(3,"fasd");
        //获取所有的key
        Set> keys = map.keySet();
       /* Iterator it = keys.iterator();
        while (it.hasNext()){
            Integer key = it.next();
            String value = map.get(key);
            System.out.println(key+"="+ value);
        }*/
       for(Integer key :keys){
           System.out.println(key+"="+map.get(key));
       }
       //第二种方法直接转换成Set集合。
        //Set集合中元素的类型是:Map.Entry
        Set.Entry,String >> set = map.entrySet();
       /*Iterator> it2 = set.iterator();
       while (it2.hasNext()){
           Map.Entry node = it2.next();
           Integer key = node.getKey();
           String  value = node.getValue();
           System.out.println(key+"="+value);
       }*/
       for(Map.Entry,String>node:set){
           System.out.println(node.getKey()+"="+node.getValue());
       }

    }
}

技术图片

HashMap集合类

  • 无序,不可重复。(不清楚元素放在哪个单链表上;底层equals方法保证了key不重复。)
  • 继承Map接口,非线程安全。
  • HashMap集合类的key和value可以为null.
  • HashMap集合底层是哈希表/散列表的数据结构
  • 哈希表是一个一维数组和数组的每一个元素是单向链表。
  • 哈希表将以上的两种数据结构融合在一起,充分发挥了两者的优点。
  • 哈希值是key的hashCode()方法的执行结果,hash值通过哈希函数/算法可以转换成数组的下标。
  • map.put(k,v)方法和 v map.get(k)方法实现原理:
    技术图片
  • HashMap集合的key部分的元素其实就是放在了HashSet集合上了,因此HashSet也需要重写equals和hashCode()方法。
  • 哈希表HashMap使用不当时无法发挥性能,假设所有的hashCode()方法返回一个固定值(下标相同),那么会导致哈希表成了单向链表,我们称为散列分布不均匀。但是如果hashCode方法每次都返回不一样的值(下标不同,存在不同的哈希值对应相同的下标),这时就成了数组,这也是分布不均匀的情况。因此如何重写hashCode()方法很重要。
    重点:重写放在HashMap集合key部分的元素和HashSet集合中的元素,需要同时重写hashCode和equals方法。
  • HashMap集合的默认初始化容量位16(起初化容量必须是2的倍数,因为这样能达到散列均匀,提高该集合的存取效率,否则性能不佳),默认加载因子是0.75,这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容(扩容时原来的两倍)Hashset跟HashMap相同。
  • Map集合存取,都是先调用key和hashCode方法,然后再调用equals方法。equalf方法可能调用(当下标位置为null时不会调用equals)。
  • 如果一个类的equals方法重写了,其hashCode方法必须重写。并且equals方法返回时true时,hashCode方法返回的值必须相同。
  • HashMap集合key部分和HashSet需要同时重写同时重写hashCode方法和equals方法:IDEA生成快捷键,Alt+Insert.
  • HashMap集合在Jdk8以后:如果单向链表元素超过8个,单项链表会变成红黑树数据结构,当红黑树节点数小于6时,又会重新变成单向链表数据结构。
public class HashMapTest1 {
    public static void main(String[] args) {
        Map,String> map = new HashMap>();
        map.put(888,"fas");
        map.put(888,"fasf");
        map.put(77,"fa");
        map.put(55,"fdfda");
        System.out.println(map.size());
        Set.Entry,String>> set = map.entrySet();
        for(Map.Entry,String > a: set){
            System.out.println(a.getKey()+"-->"+a.getValue());
        }
    }
}

Hashtable集合类

  • Hashtable的key和value都不可以为null.
  • Hashtable方法都带有synchronized线程安全,但是效率较低,现在有其它方案对线程的处理。
  • 底层和HashMap一样都是哈希表,初始化容量为11,扩容因子0.75,扩容为原容量的2倍加1.

Properties集合类

  • 父类为Hashtable,是一个Map集合。
  • Properties的key和value都是String类型。
  • Properties为属性对象。
  • setProperty()方法:存
  • getProperty()方法:取
public class PropertiseTest1 {
    public static void main(String[] args) {
        Properties pro= new Properties();
        pro.setProperty("url","dfaf");
        pro.setProperty("fasd","fasd");
        //通过key获取value
        String a=pro.getProperty("url");
        String b= pro.getProperty("fasd");
        String c =pro.getProperty("url");
        String d =pro.getProperty("fasd");
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }
}
&gt


评论


亲,登录后才可以留言!