18、泛型和集合(02)

目录

一、List(下)

1、链式存储结构

单链表概述

双链表概述

2、Vector类详解

Vector类概述

Vector类演示

二、泛型Generics

1、泛型的概述

泛型的引入

2、泛型的使用

三、Map接口

1、Map接口介绍

Map常用方法

keySet()方法

entrySet()方法

2、HashMap类使用详解

情况一:key为常用类对象

情况二:key为自定义对象


一、List(下)

1、链式存储结构

单链表概述

单链表定义

单链表采用的是链式存储结构,使用一组地址任意的存储单元来存放数据元素。在单链表中,存储的每一条数据都是以节点来表示的,每个节点的构成为:元素(存储数据的存储单元) +  指针(存储下一个节点的地址值),单链表的节点结构如下图所示:

另外,单链表中的开始节点,我们又称之为首节点;单链表中的终端节点,我们又称之为尾节点。如下图所示:

根据序号获取节点的操作

在线性表中,每个节点都有一个唯一的序号,该序号是从0开始递增的。通过序号获取单链表的节点时,我们需要从单链表的首节点开始,从前往后循环遍历,直到遇到查询序号所对应的节点时为止。

以下图为例,我们需要获得序号为2的节点,那么就需要依次遍历获得“节点11”和“节点22”,然后才能获得序号为2的节点,也就是“节点33”。

因此,在链表中通过序号获得节点的操作效率是非常低的,查询的时间复杂度为O(n)。

根据序号删除节点的操作

根据序号删除节点的操作,我们首先应该根据序号获得需要删除的节点,然后让“删除节点的前一个节点”指向“删除节点的后一个节点”,这样就实现了节点的删除操作。

以下图为例,我们需要删除序号为2的节点,那么就让“节点22”指向“节点44”即可,这样就删除了序号为2的节点,也就是删除了“节点33”。

通过序号来删除节点,时间主要浪费在找正确的删除位置上,故时间复杂度为O(n)。但是,单论删除的操作,也就是无需考虑定位到删除节点的位置,那么删除操作的时间复杂度就是O(1)。

根据序号插入节点的操作

根据序号插入节点的操作,我们首先应该根据序号找到插入的节点位置,然后让“插入位置的上一个节点”指向“新插入的节点”,然后再让“新插入的节点”指向“插入位置的节点”,这样就实现了节点的插入操作。

以下图为例,我们需要在序号为2的位置插入元素值“00”,首先先把字符串“00”封装为一个节点对象,然后就让“节点22”指向“新节点00”,最后再让“节点00”指向“节点33”,这样就插入了一个新节点。

通过序号来插入节点,时间主要浪费在找正确的插入位置上,故时间复杂度为O(n)。但是,单论插入的操作,也就是无需考虑定位到插入节点的位置,那么插入操作的时间复杂度就是O(1)。

双链表概述

双链表的定义

双链表也叫双向链表,它依旧采用的是链式存储结构。在双链表中,每个节点中都有两个指针,分别指向直接前驱节点(保存前一个节点的地址值)和直接后继节点(保存后一个节点的地址值),如下图所示。

所以,从双链表中的任意一个节点开始,都可以很方便地访问它的直接前驱节点和直接后继节点,如下图所示。

单链表和双链表的区别

逻辑上没有区别,他们均是完成线性表的内容,主要的区别是结构上的构造有所区别。 

1)单链表

对于一个节点,有储存数据的data和指向下一个节点的next。也就是说,单链表的遍历操作都得通过前节点—>后节点。

2)双链表

对于一个节点,有储存数据的data和指向下一个节点的next,还有一个指向前一个节点的pre。也就是说,双链表不但可以通过前节点—>后节点,还可以通过后节点—>前节点。

2、Vector类详解

Vector类概述

Vector类和ArrayList类的用法几乎一模一样,底层都是采用了数组结构,很多情况下可以互用。只不过Vector类的方法都加了同步检查,因此“线程安全,效率低”。 

比如:add(E e)方法就增加了synchronized同步标记。

相比较于ArrayList,Vector还包含了许多传统的方法,虽然这些方法不属于集合框架。

Vector类演示

public class Test {
	public static void main(String[] args) {
		// 实例化一个Vector
		Vector<String> vector = new Vector<String>();
		// 添加元素,addElement()方法和add()方法类似
		vector.addElement("java");
		vector.addElement("HTML");
		vector.addElement("JavaScript");
		vector.addElement("CSS");
		// 遍历集合, elements()方法类似于iterator()方法
		Enumeration<String> elements = vector.elements();
		while(elements.hasMoreElements()) {
			System.out.println(elements.nextElement());
		}
	}
}

【新手建议】如何选用ArrayList、LinkedList、Vector?

需要保证线程安全时,建议选用Vector。

不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)。

不存在线程安全问题时,增加或删除元素较多用LinkedList。

二、泛型Generics

1、泛型的概述

泛型是JDK5.0以后增加的,他可以帮助我们建立类型安全的集合。在使用了泛型后,不必进行强制类型转换。JDK提供了支持泛型的编译器,将运行时的类型检查提前到了编译时执行,使代码可读性和安全性更高。

泛型的引入

在JDK1.5之前,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

我们先看下面的代码:

ArrayList list = new ArrayList();
list.add("111");
list.add(222);
for (int i = 0; i < list.size(); i++) {
	String str = (String)list.get(i);
	System.out.println(str);
}

以上示例中,向list类型集合中加入了一个String类型的值和一个Integer类型的值。在之后的循环中,由于在list中加入了Integer类型的值,如果直接做强转操作会抛出java.lang.ClassCastException异常。为了解决这个问题,泛型应运而生。

2、泛型的使用

泛型的本质就是“数据类型的参数化”。 我们可以把“泛型”理解为类型的一个占位符(形式参数),即告诉编译器,在调用泛型时必须传入实际类型。

【例】泛型的使用案例

// 使用泛型,明确ArrayList存储的数据类型
ArrayList<String> list = new ArrayList<String>();
list.add("111"); // 只能添加String类型数据
list.add("222");
// list.add(333); 存储非String类型数据,编译失败
// 通过循环取出集合中的元素
for (int i = 0; i < list.size(); i++) {
	// 因为list明确了存储的数据类型,所以此处不用做强转
	String str = list.get(i);
	System.out.println(str);
}

泛型的出现避免了强制转换的麻烦,同时把运行时期的ClassCastException异常提前到编译时期。急哦,

三、Map接口

1、Map接口介绍

现实生活中,我们经常需要成对存储某些信息。比如,我们使用的微信,一个手机号只能对应一个微信账户。这就是一种成对存储的关系。

Map就是用来存储“键(key)-值(value) 对”的。Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复。

Map接口位于java.util包中,开发中Map接口最常用的实现类有HashMap、LinkedHashMap、TreeMap、Hashtable、Properties等等。

Map常用方法

Map接口中定义的方法

Object put(Object key, Object value);

存放键值对。

Object get(Object key);

通过键对象查找得到值对象。

Object remove(Object key);

删除键对象对应的键值对。

boolean containsKey(Object key);

Map容器中是否包含键对象对应的键值对。

boolean containsValue(Object value);

Map容器中是否包含值对象对应的键值对。

Collection values();

获取集合中所有的值

int size();

获取包含键值对的数量。

boolean isEmpty();

判断Map是否为空。

void clear();

清空本map对象所有键值对。

【例】Map接口方法使用

// 创建一个map容器
Map<Integer, String> map = new HashMap<Integer, String>();
// 添加键值对
map.put(11, "zhansan");
map.put(4, "lisi");
map.put(6, "wangmazi");
map.put(6, "xiaowang");
// 获取键值对个数
System.out.println(map.size());
// 判断Map是否为空
System.out.println(map.isEmpty());
// 根据键获取值
System.out.println(map.get(11));
System.out.println(map.get(12));
// Map容器中是否包含键对象对应的键值对
System.out.println(map.containsKey(111));
// Map容器中是否包含值对象对应的键值对
System.out.println(map.containsValue("lisi"));
// 获取集合中所有的值
Collection<String> values = map.values();
// 删除键对象对应的键值对
map.remove(11);
// 清空本map对象所有键值对
map.clear();

Map集合中提供了get() 获取元素的方法, get()对应一个键取出其对应的值,这种方式比较局限和单一,不能全部取出来。要取出所有的元素(值),则必须要拿到所有的键,然后才能取到所有与其对应的值,针对这种新的需求,Java中提供了相应的解决方案。

Map集合中是没有迭代器的,Map集合取出键值的原理:将Map集合转成Set集合,再通过迭代器取出 。

keySet()方法

Set<K> keySet()方法,该返回此映射中包含的键的Set视图将map中所有的键存入到Set集合,因为set具备迭代器,所有迭代方式取出所有的键再根据get()方法,获取每一个键对应的值。

【例】keySet() 方法使用

// 创建一个map容器
Map<Integer, String> map = new HashMap<Integer, String>();
// 添加键值对
map.put(11, "zhansan");
map.put(4, "lisi");
map.put(6, "wangmazi");
map.put(7, "xiaowang");
// 获取键(key)的set集合
Set<Integer> set = map.keySet();
// 迭代器
Iterator<Integer> iterator = set.iterator();
while(iterator.hasNext()) {
	// 获取到键key
	Integer key = iterator.next();
	// 输出map的key和value
	System.out.println("key: " + key + " value: " + map.get(key));
}

entrySet()方法

entrySet()方法,该方法取出的是关系(Set<Map.Entry<K, V>>),关系中包含key和value。其中Map.Entry<K,V>来表示这种数据类型,即将Map集合中的映射关系存入到Set集合中,这个关系的数据类型为Map.Entry接口。

Map.Entry接口在java.util包中,它是Map接口中的一个内部接口,getKey()和getValue()是接口Map.Entry<K,V>中的方法,返回对应的键和对应的值。

【例】entrySet () 方法使用

// 创建一个map容器
Map<Integer, String> map = new HashMap<Integer, String>();
// 添加键值对
map.put(11, "zhansan");
map.put(4, "lisi");
map.put(6, "wangmazi");
map.put(7, "xiaowang");
// entrySet方法
Set<Entry<Integer, String>> entrySet = map.entrySet();
// 获取迭代器
Iterator<Entry<Integer, String>> iterator = entrySet.iterator();
while(iterator.hasNext()) {
	// 获取Entry对象
	Entry<Integer, String> next = iterator.next();
	// 输出map的key和value
	System.out.println("key: " + next.getKey() + " value: " + next.getValue());
}

2、HashMap类使用详解

HashMap采用哈希算法实现,是Map接口最常用的实现类。 由于底层采用了哈希表存储数据,我们要求键不能重复,如果发生重复,新键值对会替换旧的键值对。 HashMap在查找、删除、修改方面都有非常高的效率。

HashMap 集合中的 key 不能重复(key可以为null),因此我们需要通过重写 hashCode() 与 equals()方法来保证Key的唯一性。

情况一:key为常用类对象

HashMap 中key为 JavaAPI 中提供的类型元素时,不需要重写元素的 hashCode 和 equals 方法,因为这两个方法,在 JavaAPI 的每个类中已经重写完毕,如 String 类、Integer 类等。

【例】HashMap中key为 String类型

public static void main(String[] args) {
// 初始化HashMap对象
Map<String, Integer> map = new HashMap<String, Integer>();
// 添加元素
map.put("a", 111);
map.put("c", 222); // 被覆盖
map.put("e", 333);
map.put("d", 444);
map.put("b", 555);
map.put("c", 222); // 与第二个添加的key相同,那么覆盖第二个的value值
// 遍历元素
Iterator<String> iterator = map.keySet().iterator();
	while(iterator.hasNext()) {
		String key = iterator.next();
		System.out.println("key:" + key + " value:" + map.get(key));
	}
}

输出结果如下:

key:a value:111

key:b value:555

key:c value:222

key:d value:444

key:e value:333

注意:当在HashMap 中put的key在之前已经存过,则不会重复存储,会覆盖之前key对应的value。

情况二:key为自定义对象

当给 HashMap 中存放自定义对象时,如果自定义对象作为 key 存在,这时要保证对象唯一,必须重写对象的 hashCode 和 equals 方法,建立自己的比较方式,才能保证 HashMap 集合中的对象唯一。

例如,每位学生(姓名,年龄)都有自己的家庭住址。那么既然有对应关系,则将学生对象和家庭住址存储到HashMap 集合中。学生作为键(key),家庭住址作为值(value),当学生姓名相同并且年龄相同视为同一名学生。

【例】定义Student类

public class Student {
    // 成员变量
    String name;
    int age;
    // 构造方法
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    @Override
    public String toString() {
        return "Student{name=" + name + ", age=" + age + "}";
    }
}

【例】创建 HashMap 集合

public class Test04 {
    public static void main(String[] args) {
        // 初始化HashMap对象
        HashMap<Student, String> map = new HashMap<>();
        // 添加元素
        map.put(new Student("张三", 33), "成都");
        map.put(new Student("李四", 19), "成都");
        map.put(new Student("王五", 18), "重庆");
        map.put(new Student("赵六", 28), "北京");
        map.put(new Student("赵六", 28), "上海"); // 只能输出一个
        // 遍历元素
        Iterator<Student> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Student stu = iterator.next();
            System.out.println("key:" + stu + " value:" + map.get(stu));
        }
    }
}

输出结果如下:

key:Student{name=王五, age=18} value:重庆

key:Student{name=张三, age=33} value:成都

key:Student{name=李四, age=19} value:成都

key:Student{name=赵六, age=28} value:上海

注意:当自定义对象作为HashMap的key时,一定得重写自定义类的 hashCode 和 equals 方法,建立自己的比较方式,才能保证 HashMap 集合中的对象唯一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值