JAVA中的Collection,List,Set,Map集合的使用与万字详解

集合中主要的接口概述       

在JAVA.until包中提供了一些集合,常用的集合有List集合,Set集合和Map集合,其中List集合的List接口和Set接口实现了Collection接口,这些集合被称为容器。集合和数组不同,数组的长度是固定的,集合的长度是可变的;数组只能存放基本数据类型且只能存储一种类型,集合用来存放类对象的引用并且可以存储同一种类型。

集合框架被设计成要满足以下几个目标。

  • 该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。

  • 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。

  • 对一个集合的扩展和适应必须是简单的。

   List接口,Set接口,Map接口和Collection接口的主要特性如下           

  • 1、List列表:有序的,可重复的;

  • 2、Queue队列:有序,可重复的;

  • 3、Set集合:不可重复;

  • 4、Map映射:无序,键唯一,值不唯一。

上述集合接口继承关系 

  • 接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
  • 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
  • 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。

从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayListLinkedListHashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。

List集合的用法

List集合包括List接口以及List接口的实现类。List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。

List 接口存储一组不唯一,有序(插入顺序)的对象。

方法名称功能简介
add(int index,Object obj)用来向集合的指定索引位置添加对象
addAll(int,Collection coll)用来向集合的指定索引位置添加指定集合中的对象
remove(int index)用来清除集合中指定索引位置的对象
set(int index,Object)用来将集合中指定索引位置的对象修改为指定对象
get(int index)用来获得指定索引位置的对象
indexOf(Object obj)用来获得指定对象的索引对象位置。当存在多个是,返回第一个的索引位置,当不存在时,返回-1;

List接口的常用实现类有ArrayList和LinkedList。在使用List集合时,通常情况下,声明为List类型,实例变化时,根据实际情况的需要为ArrayList或LinkedList,例如:

 List<String>  l1 =new ArrayList<>();
 List<String>  l2 =new LinkedList<>();

ArrayList和LinkedList之间有什么区别

(1)ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
(2)LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素

ArrayList

ArrayList类实现了List接口,由ArrayList类实现的List集合采用数组结构保存对象。

优点:基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。

缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。如果向指定索引插入一个对象,那么后面所有的对象都将向后移动一位。

LinkedList

LinkedList类实现了List接口,由LinkedList类接口实现List集合采用链表结构保存对象。

相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引

LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景

缺点在查询时必须从头开始,一个一个地访问每个节点。 如果要查询链接列表中的某一项,需要从头开始遍历,直到找到该项为止。LinkedList要移动指针,所以查询操作性能比较低。

LinkedList 需要更多的内存,因为 ArrayList 的每个索引的位置是实际的数据,而 LinkedList 中的每个节点中存储的是实际的数据和前后节点的位置 ( 一个 LinkedList 实例存储了两个值: Node<E> first 和 Node<E> last 分别表示链表的其实节点和尾节点,每个 Node 实例存储了三个值: E item,Node next,Node pre) 。

LinkedList一些方法的使用

public class LinkedList1 {
    public static void main(String[] args) {
        String a ="A",b="B",c="C",text="Text";
        LinkedList<String> list=new LinkedList<>();
        list.add(a);
        list.add(b);
        list.add(c);
        System.out.println(list.getFirst());//获得并输出集合开头的对象
        list.addFirst(text);                //向集合开头添加一个对象
        System.out.println(list.getFirst());
        list.removeFirst();                 //移除集合开头的对象
        System.out.println(list.getFirst());
    }
}

运行结果

list的其他常规操作

import java.util.List;

public class Text {
    public static void main(String[] args) {
        List<String> list =new ArrayList<String>();
        list.add("Hello");
        list.add("World");
        list.add("HAHAHAHA");

        boolean isEmpty =list.isEmpty();//返回true,如果列表为空
        int index =list.indexOf(1);//返回元素“Hello”的索引,如果不存在则返回-1
        Collections.reverse(list);//反转列表中的元素的顺序
        List<String> subList =list.subList(1,3);//获取索引1到2之间的子列表
        list.set(0,"大帅比");//替换第一个元素为“大帅比”

    }
}

如何使用迭代器

通常情况下,你会希望遍历一个集合中的元素。例如,显示集合中的每个元素。

一般遍历数组都是采用for循环或者增强for,这两个方法也可以用在集合框架,但是还有一种方法是采用迭代器遍历集合框架,它是一个对象,实现了Iterator 接口或 ListIterator接口。

迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了 Iterator,以允许双向遍历列表和修改元素。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Text {
    public static void main(String[] args) {
        List<String> list =new ArrayList<String>();
        list.add("Hello");
        list.add("World");
        list.add("HAHAHAHA");
        //第一种遍历方式使用For-each遍历list
        for(String str:list){
            System.out.println(str);
        }
        //第二种遍历是使用迭代器
        Iterator<String> ite =list.iterator();
        while(ite.hasNext()){//判断下一个元素之后的值
            System.out.println(ite.next());

        }
    }
}

Set集合

Set集合为集类型,用于存放集中的对象不按特定方式排列,只是简单的将对象加入到集合中,类似于向口袋里放东西。对集中存放的对象的访问和操作是通过对象的引用进行的,所以,在集中不能存放重复对象。

 Set有多个实现类,其中最常见的是HashSet和TreeSet。HashSet底层是使用HashMap实现的,而TreeSet则是使用红黑树实现的。HashSet的操作速度比较快,但其元素是无序的;而TreeSet的元素是有序的,但操作速度相对较慢。

  1. HashSet:基于哈希表实现,具有快速的插入、删除和查找操作,适用于需要快速查找的场景。
  2. TreeSet:基于红黑树实现,可以对元素进行排序,并提供了一系列与排序相关的方法,适用于需要排序功能的场景。
  3. LinkedHashSet:基于哈希表和链表实现,保持元素的插入顺序,适用于需要保持插入顺序的场景。

Set的常用方法包括add()用来添加元素、remove()用来删除元素、contains()用来判断是否包含某个元素、isEmpty()用来判断集合是否为空、size()用来获取集合中元素的数量等等。

1.哈希表

  • HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
  • HashSet 允许有 null 值。
  • HashSet 是无序的,即不会记录插入的顺序。
  • HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。
  • HashSet 实现了 Set 接口。

2.HashSet集合的使用方法

import java.util.HashSet;

public class RunoobText {
    public static void main(String[] args) {
        HashSet<String> sites =new HashSet<>();
        sites.add("Goodle");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Zhihu");
        sites.add("Runoob");//重复的元素不会被添加

        System.out.println(sites);
        System.out.println(sites.contains("Taobao"));//查询是否包含Taobao

        sites.remove("Taobao");
        System.out.println("----------");
        System.out.println("删除后打印结果");
        System.out.println(sites);

        System.out.println("----------");
        System.out.println("遍历sites");
        
        System.out.println(sites.size());//获取Set集合中的长度
        for (String i :sites){
            System.out.println(i);
        }
    }
}

去重原理

在java中,Set集合去重原理主要是依赖于集中元素的equals()方法与hashCode()方法正确实现。

HashSet如何去重?

  Set调用 add 方法时,调用了添加对象的 hashCode方法和 equals 方法:如果Set集合中没有与该元素 hashCode 值相同的元素,则说明该元素是新元素,可以添加到 Set 集合中;如果两个元素的 hashCode 值相同,再使用 equals 方法比较,如果返回值为 true,就说明两个元素相同,新元素不会被添加到 Set 集合中;如果 hashCode 值相同,但是 equals 方法比较后,返回值为 false ,就说明两个元素不相同,新元素会被添加到 Set 集合中。

  一般来说,一个类必要时会同时重写hashCode()和equals()两个方法(如果没有重写,那么就默认调用Object中的hashCode()和equals())。这也是HashSet去重机制的关键。

public class test {
  public static void main(String[] args) {
 
    User user1 = new User("张三","男",20);
    User user2 = new User("张三","男",20);
    Set<User> users = new HashSet<User>();
    users.add(user1);
    users.add(user2);
         
    //遍历
    Iterator<User> iterator = users.iterator();
    while(iterator.hasNext()) {
        System.out.println(iterator.next());
    }
  }
}

当没有重写hashCode()和equals()时

运行结果:

张三--男----20
张三--男----20

从运行结果可以看到user1和uer2都被添加进HashSet集合中。因为我这里没有重写hashCode()和equals(),所以这里默认使用了Object类中的hashCode(),通过Object中hashCode()得出的user1和user2的哈希值是不同的,因为他们的存放地址是不一样。即直接判定这两个对象是不同的,可以同时放入HashSet集合。

如果两个对象即使存储位置不同,只要属性完全相同就不能存放进HashSet。这时需要重写hashCode()和equals()方法。

Set集合的去重机制依赖于这两个方法。如果这两个方法没有被正确地重写,那么Set集合可能无法正确地进行去重。

例如,如果你有两个不同的对象,但它们的equals()方法返回true,那么这两个对象将被视为重复,并且只能有一个被添加到Set中。同样,如果两个不同的对象有相同的哈希码,但它们的equals()方法返回false,那么这两个对象都可能会被添加到Set中,这可能会导致集合中出现重复的元素。因此,正确地重写equals()和hashCode()方法对于确保Set集合能正确去重非常重要。

如果即想保留HashSet类快速定位集合中对象的优点,又想让集合中的对象按插入到顺序保存,可以通过HashSet类的子类LinkedHash实现Set集合。

 Set<String> hashSet =new LinkedHashSet<>();

public class Demo5 {
	public static void main(String[] args) {
		Set set = new LinkedHashSet<>();
		set.add("花花");
		set.add("鸟鸟");
		set.add("猫猫");
		set.add("哈哈");
		for (Object object : set) {
			System.out.println(object);// 输出:花花 鸟鸟 猫猫 哈哈
		}
	}

Map集合

  • HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
  • HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
  • HashMap 是无序的,即不会记录插入的顺序。
  • HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。

Map集合映射中存储的每一个对象都有一个相应的键(key)对象,在检索对象必须通过相应的键对象来获取值(value)对象。 

  • 实现:HashMap 是基于哈希表实现的,它使用一个数组(称为buckets或slots)来存储键值对(键和值的映射)。每个键首先通过一个哈希函数转换成一个哈希码,然后哈希码对数组长度进行模运算以确定数组中的位置,也就是buckets的索引。
  • 增删效率O(1) ~ O(n)
  • 查询效率: O(1) ~ O(n)
  • 是否线程安全:HashMap 是非线程安全。
  • 特点
  • 当我们往 HashMap 中不断添加元素时,HashMap 会自动进行扩容操作(条件是元素数量达到负载因子(load factor = 0.75)乘以数组长度时),以保证其存储的元素数量不会超出其容量限制。
  • 在进行扩容操作时,HashMap 会先将数组的长度扩大一倍,然后将原来的元素重新散列到新的数组中。
  • HashMap 的扩容是通过 resize 方法来实现的,JDK 8 中融入了红黑树(链表长度超过 8 的时候,会将链表转化为红黑树来提高查询效率)

HashMap 的 key 与 value 类型可以相同也可以不同,可以是字符串(String)类型的 key 和 value,也可以是整型(Integer)的 key 和字符串(String)类型的 value。

下面我们来介绍HashMap的使用方法

1.创建Map集合对象

// 使用构造函数创建HashMap对象 
Map<String, Integer> map1 = new HashMap<>();

2.添加元素

public class RunoobText {
    public static void main(String[] args) {
        //HashMap
        Map<String, String> map = new HashMap<>();
        map.put("name","一一哥");
        //map.put("name","壹哥");
        map.put("age", "30");
        map.put("sex", "男");
        System.out.println("map="+map);
    }


}

运行结果:

map={sex=男, name=一一哥, age=30}

3.获取元素

从HashMap集合中获取元素的方式和List集合不同,需要使用键来获取值。HashMap集合提供了两种方法来获取值,一种是使用get()方法,另一种是使用getOrDefault()方法。如果指定的键不存在,使用get()方法会返回null,而getOrDefault()方法则会返回指定的默认值。下面是从HashMap集合中获取元素的代码示例:

public class RunoobText {
    public static void main(String[] args) {
        //HashMap
        Map<String, String> map = new HashMap<>();
        map.put("name","一一哥");
        map.put("age", "30");
        map.put("sex", "男");

        //根据key获取指定的元素
        String name = map.get("name");
        String age = map.get("age");
        //根据key获取指定的元素,并设置默认值
        String sex = map.getOrDefault("sex","男");
        String height = map.getOrDefault("height","0");
        System.out.println("[name]="+name+",[age]="+age+",[sex]="+sex+",[height]="+height);
    }

}

运行结果:

[name]=一一哥,[age]=30,[sex]=男,[height]=0

3. 判断Map集合是否包含指定的键或值

如果我们想判断HashMap集合是否包含指定的键或值,可以使用containsKey()和containsValue()方法。如果Map集合包含指定的键或值,则返回true,否则返回false。下面是判断HashMap集合是否包含指定键或值的代码示例:

public class RunoobText {
    public static void main(String[] args) {
        //HashMap
        Map<String, String> map = new HashMap<>();
        map.put("name","一一哥");
        map.put("age", "30");
        map.put("sex", "男");

        boolean containsKey = map.containsKey("name");

        // 判断HashMap是否包含指定值
        boolean containsValue = map.containsValue("一一哥");
        System.out.println(containsKey);
        System.out.println(containsValue);



    }

}

 运行结果:

true
true

4.Map的遍历方法

public class RunoobTest {
    public static void main(String[] args) {
        // 创建 HashMap 对象 Sites
        HashMap<Integer, String> Sites = new HashMap<Integer, String>();
        // 添加键值对
        Sites.put(1, "Google");
        Sites.put(2, "Runoob");
        Sites.put(3, "Taobao");
        Sites.put(4, "Zhihu");
        // 输出 key 和 value
        for (Integer i : Sites.keySet()) {
            System.out.println("key: " + i + " value: " + Sites.get(i));
        }
        // 返回所有 value 值
        for(String value: Sites.values()) {
          // 输出每一个value
          System.out.print(value + ", ");
        }
    }
}

运行结果:

key: 1 value: Google
key: 2 value: Runoob
key: 3 value: Taobao
key: 4 value: Zhihu
Google, Runoob, Taobao, Zhihu, 
entrySet

entrySet是 java中 键-值 对的集合,Set里面的类型是Map.Entry,一般可以通过map.entrySet()得到。

  • entrySet实现了Set接口,里面存放的是键值对。一个K对应一个V。

用来遍历map的一种方法。

Set<Map.Entry<String, String>> entryseSet=map.entrySet();
 
for (Map.Entry<String, String> entry:entryseSet) {
 
    System.out.println(entry.getKey()+","+entry.getValue());
 

}

  • 37
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值