Java核心技术读书笔记——集合

本笔记为读《Java核心技术 卷1 第9版》而记录

1.集合接口与实现相互分离

1、Java的集合类库也将接口和实现相分离。

2、队列有两种实现方式,一种是循环数组,一种是链表
如果想要用循环数组,则使用ArrayDeque
如果想要用链表,则使用LinkedList

3、当程序一旦构建了集合时,就不需要知道究竟使用了哪种集合,因此,只有在构建集合时使用具体的类才有意义。
一个好习惯,就是用接口类型存放集合的引用。

Queue<Customer> example = new ArrayDeque<>();

利用这种方式,一旦改变了想法,也能轻松地转变为另外一种实现。

4、接口本身不会说明哪种实现的效率究竟如何,比如在队列中,循环数组比链表更高效,但缺点是循环数组是个有界数组。(有弊必有利)

5、研究API文档会发现一个Abstract开头的类,如AbstractQueue,这个类是专门为类库实现者而设计的。
如果要自己的队列类,会发现拓展AbstractQueue类比实现实现Queue中的所有方法容易得多。

1.1Java类库中集合接口和迭代器接口

java中集合类的基本接口是Collections,基本方法为
在这里插入图片描述Iterator方法用于返回实现Iterator接口的对象,可以使用这个迭代器依次访问集合中的元素。

在这里插入图片描述for each循环可以和任何实现了Iterable接口的对象一起工作。
Iterable只有一个方法。
在这里插入图片描述Collection接口拓展了Iterable接口,因此Java中标准类库的任何集合都可以使用for each循环。

在java中,迭代器的查找操作和位置变更操作是一起执行的,因此应该把他看做是夹在两个元素之间,使用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素引用。

在这里插入图片描述在迭代器中,remove操作要和next操作一起使用,remove删除的元素是next刚刚越过的元素,如果出现以下情况则会报错。

Iterator<String> it = c.iterator();
it.next();
it.remove();
//没有使用next,报错
it.remove();
1.2泛型实用方法

java设计者认为某些实用方法非常有用,应该设计为接口中的方法,Collections接口就含有大量这样的方法。
在这里插入图片描述但是实现Collection接口的每一个类都要完成这么多的例行方法,是非常烦人的。
因此,设计师们又设计了AbstractCollection接口,将基础方法Iterator和size抽象化,提供了例行方法的实现。
如此以后如果有类设计师想要一个具体的类时,就不需要实现这么多东西了。

2.具体的集合

这里的集合不包括线程安全的。
在这里插入图片描述

2.1链表

在Java程序语言设计中,所有的链表实际上都是双向链接的

链表是个有序集合,每个对象的集合非常重要。
LinkedList.add()将对象添加到链表的尾部。

但是经常需要将元素添加到链表中间,就要用到Iterator,只有对自然有序的集合使用迭代器添加元素,才有意义。
如:set中,其中元素完全无序,因此Iterator中没有add方法,
为此java设计者设计子接口ListIterator,其中就有add方法,其不返回boolean值,假定每次add都会改变链表结构,除此之外,ListIterator还有两个方法用来反向遍历链表。
在这里插入图片描述具体操作跟next方法都是在两个元素夹缝中间,但是返回的是光标右侧的元素。
通过链表实现的结合可以使用
在这里插入图片描述listIterator来返回一个实现了ListIterator接口的迭代对象。
可以非常容易地对链表中间进行增加元素。

可以预想到,这个add方法可以在链表头和链表尾部中进行添加。
假设有”A“、”B“、”C“三个元素,那么可以添加的位置有如下
在这里插入图片描述ListIterator中还有set方法,可以把迭代器相应位置的元素进行取代。

当一个集合中存在多个迭代器,一个迭代器发现集合被另外一个迭代器修改了元素的时候,会抛出异常,防止并发问题。
(其中,set方法不在此列,set进行修改元素后,并不会抛出异常)

在LinkedList类中,提供了一个get方法,用来访问某个特定元素,但是链表本身是不支持快速地随机访问的,这个get方法效率不会很好,绝对不能使用让人误解的方式来对LinkedList进行遍历

//非常错误的做法
for(int i = 0;  i< list.size();i++){
    list.get(i);
}

在对LinkedList进行增删操作的时候,画出一个光标图,将会对代码编写或是理解会有很大的好处。

2.2数组列表

链表可以用Iterator,另一种是使用get和set。对于链表,不推荐使用后者,但是对于数组,则完全不同。

需要线程同步的时候,使用Vector。
不需要的时候,使用ArrayList。

2.3散列表

散列表为每个对象计算一个整数,称为散列码,散列码是由对象的实例域中产生的一个整数,更准确的说,具有不同数据域的对象将产生不同的散列码。
在这里插入图片描述在Java中,散列表用链表数组实现,每个列表被称为“桶”(bucket)

查找对象在表中的位置步骤:

  1. 计算散列码
  2. 与桶的总数取余
  3. 得到结果就是保存结果的桶的索引

例子:
散列码 = 76268
桶数 = 128
取余后,得到108,可以知道对象在第108个桶里面插入。
如果桶已经沾满,就发生散列冲突,需要新对象和桶中的所有对象进行比较,查看这个对象是否已经存在。
在这里插入图片描述如果想要控制散列表的运行性能,就要指定桶数。

桶数不是指有多少个列表数目,而是指一个列表中最多可以插入多少个对象。

桶数如果太大,就会增加冲突可能性,降低性能。

标准类库中,默认值为16

装填因子(load factor)用于决定散列表是否再次散列

默认为0.75,当一个散列表中给的值,超过75%时,就会自动创建一个两倍大的散列表再次装入,并把旧的表给丢弃掉。

Set类型使用的是散列表,add方法是在集合中查找添加的对象,如果不存在,则添加。

contains方法已经被重定义,只会在桶中查找元素,不必查看整张表的元素。

在散列表中使用迭代器是一种效率很低的选择,散列将元素分布在各个位置里,没有任何顺序的关系。

2.4树集

TreeSet和散列表相当类似,是散列表的改进,是一个有序集合。

以任意的顺序插入的集合中,在排序时,每个值会自动地排好序呈现。

底层原理是“红黑树”

每次添加的时候,都会被放在正确的位置上。

添加元素到正确位置的速度(消耗时间):
散列表 < 树集 < 链表 < 数组

2.5对象的比较

TreeSet如何知道元素希望该如何进行排列呢?
在默认情况,TreeSet认为插入的元素是已经实现了Comparable接口。

在这里插入图片描述a 与 b 相等,a.compareTo(b) 返回0;
a 在 b 之前,a.compareTo(b) 返回负值
a 在 b 之后,a.compareTo(b) 返回正值

标准的Java平台类是已经实现了Comparable接口的,如String,Integer类

String的排序是依照字典序来排列的

如果是用户自定义的类,那么就需要自己写Comparable接口方法。

在这里插入图片描述
如果第一项减去第二项小于0,则第一项放在前面(其实就是从小到大排序。)

局限性:

  1. 实现这个接口只能实现一次,无法做到灵活排序
  2. 如果要进行排序的类,没有实现Comparable接口,很难排序。

因此大多数情况,我们会更加偏向使用:Comparator对象传递给TreeSet构造器。

public interface Comparator<T>{
    int compare(T a,T b);
}

与compareTo方法一样,a在b之前,就返回负。

我们会更偏向于使用匿名内部类,来构造集合。

SortedSet<Item> sortByDescription = new TreeSet<>(new Comparator<Item>(){
    public int compare(Item a,Item b){
        return a.money - b.money;
    }
});

散列表和树集各有优点,如果有个一个矩形的类,那么散列表已经定义了散列函数,直接对坐标进行散列,然而树集就很难对其进行排序。

2.6队列与双端队列

在Java中引入了Deque接口,
ArrayDeque和LinkedList实现了
这两个类都提供了双端队列。

Queue接口中,add和offer的区别
在这里插入图片描述
Queue接口中,remove和poll的区别
在这里插入图片描述Queue接口中,element和peek的区别
在这里插入图片描述#### 2.7优先级队列(priorityQueue)

优先级队列中的元素可以按照任意顺序插入,总是按照顺序进行检索。

无论何时调用remove,总会获得当前优先队列最小的元素。

原理:堆(heap)

是一个可以自我调整的二叉树,对树执行了添加和删除操作,可以让最小的元素移动到根。

最常用的是任务调度,当启动一个新任务的时候,就丢弃一个优先度最高的任务(最高优先度的数字是1)

2.8映射表

映射表用于存放键、值对。

Java类库为映射表提供了两个通用实现:HashMap和TreeMap,都实现了Map接口

与集合一样,散列映射会比树映射更快,但是树有表现得好的地方。

映射表本身不是集合,然而,可以获取映射表的视图,视图都是实现了Collection接口的对象。

有三个视图:
一:
在这里插入图片描述返回的是“键”的集合
注意:这里的Set不是HashSet,也不是TreeSet

二:
在这里插入图片描述返回的是“值”的Collection视图

三、在这里插入图片描述返回的是“条目”(entries)的Set集合
条目是一个Map内部的一个静态接口,Map也是接口。
在这里插入图片描述遍历所有条目的代码:

for( Map.Entry<String,Employee>entry:staff.entrySet() ){
    String key = entry.getKey();
    Employee value = entry.getValue();    
}

在上述三个视图中,是可以进行remove操作的,但是不能add操作。(只能删,不能增)

2.9 专用集和映射表类
2.9.1弱散列映射表(WeakHashMap)

设计原因:映射表中的某个键的最后一次引用已经消亡,然而仍然存在于映射表中,程序中已经不会再出现这个键了,这个键/值对无法被删除,也无法被回收。

垃圾回收器追踪活动的对象,只要映射表是活动的,它们就不能被回收。

弱散列映射表的意义就是,当键的引用唯一来自散列表的条目的时候,他将和垃圾回收器合作一起回收这个键值对。

运行原理:

  • WeakHashMap使用弱引用保存键
  • 如果一个对象只有弱引用的时候,垃圾回收器就会去回收他
  • 将回收的弱引用放入队列
  • WeakHashMap定期的检查队列
  • 一旦一个弱引用进入了检查队列,意味着这个键不再被他人使用
  • 这个时候,WeakHashMap就会删除这个键值对
2.9.2 链接散列集 和 链接散列表

两个类:LinkedHashSet 和 LinkedHashMap

链接散列集:记住插入元素项的顺序,避免散列表是在表面看上去是随机排序的。

链接散列表:记住访问顺序,而不是插入顺序,每当有一个条目被访问,就把他在链表的当前位置删除,并放入链表尾部(注意,他在散列表中的桶的位置是不会改变的)

这个数据结构,将对实现“最近最少使用”原则相当重要。

3.集合框架

这一段有点看不下去,暂且记录几笔。

3.1集合框架的接口

在这里插入图片描述

3.2集合框架的类

在这里插入图片描述

3.3集合框架的遗留类

在这里插入图片描述

3.4 集合和数组中转换

Java中常见的需求是将传统的数组和集合进行转换。

数组转集合(简单):
使用Arrays.asList即可实现这个目的。

Set<String> staff = new HashSet<>(Arrays.asList(values));

相反,集合转数组则变得十分困难。

看起来很简单的方法

Object[] values = staff.toArray();

得到的是一个对象数组
注意:即使你知道这个对象是什么类型,你也不能使用强制类型转换,数组不支持。

应该是用另一种方法:

        Set<String> set = new HashSet<>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
		
		//方法1
        String[] values = set.toArray(new String[0]);
        //方法2
        String[] values = set.toArray(new String[set.size()]);
        for (String value : values) {
            System.out.println(value);
        }

注意:这种方法不适用与Int数组和Integer数组之间的转换。
(这里又要涉及到Java中Int和Integer之间的不同。)

4.算法

泛型集合接口有一个很大的优点,即算法只需要实现一次。
比如:
所有集合都要实现Iterator这个接口,如果是要进行寻找最大元素,所有集合都可以用Iterator来进行查找。
在这里插入图片描述

4.1排序与混排

Collections类中sort方法实现了对List的排序,假定列表元素实现了

Comparable接口,如果想要自定义排序,像之前说的,传入Comparator进方法作为第二个参数即可。

如果想要进行排序,有个更方便的方法。
即使用

Collections.sort(item,	Collections.reversOrder());

其实,只不过是Comparator反过来写了,本质还是Comparator的重写。

对Java的列表进行随机访问方式进行排序效率是很低的,因此Java采用的是一种归并排序的变体对数组进行排序。

步骤:

  1. 列表转换为数组
  2. 数组进行归并排序的变体算法
  3. 数组复制回列表

速度要慢于快排,优点:稳定,不需要交换相同的元素。

Collections中还有shuffle方法,与排序功能相反,随机排序元素顺序。

4.2二分查找

Collections类中的binarySearch就是二分查找。

注意,集合必须是排好序的。
否则将返回错误的答案。

必须采用随机访问,二分查找才有意义,如果传入一个链表,二分查找会自动转变为线性查找。

4.3简单算法

实现这些算法的目的:可以让程序猿阅读算法变得轻松,而不是看到别人的循环揣测别人的意图。

包括:
1、max
2、min
3、copy(to,form)复制列表中的所有元素到目标列表上
4、fill
5、addAll(collection,value…)将所有的值加到列表上
6、replaceAll(collection,oldvalue,newvalue)将新的值取代旧的值
7、indexOfSubList
8、lastIndexOfSubList
9、swap(list,i,j)
10、reverse(list) 逆置列表中元素的顺序
11、rotate (list,int d)
旋转列表中的元素,将索引i的条目移动到(i+d)%l.size()的位置。
如:【t,a,r】 旋转两个位置
得:【a,r,t】
12、frequency (collection,object)返回c中与o相同的元素个数
13、disjoint(collection,collection)如果两个列表没有相同的元素,返回true

5.遗留的集合

5.1 Hashtable

Hashtbale与HashMap作用一样,他们拥有相同的接口。

与Vector类方法一样,如果对同步性或遗留性代码没有要求,应该使用HashMap。

注意,Hashtable的t是一个小写

5.2属性映射表

property map是一个非常特殊的映射表

特性:

  1. 键与值都是字符串
  2. 表可以保存在一个文件,也可以从文件中加载
  3. 使用一个默认的辅助表

在这里插入图片描述

5.3栈

Stack在java中自从1.0版本就存在,但是拓展的是Vector类。

这能够让他使用不属于栈inser方法和remove方法,从理论角度上说是不好的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值