Java核心技术——第六章:链表、数组列表、散列集、映射

链表

链表(linked list)将每一个对象放在独立的节点中,每个节点还存放着下一个和下一个节点的引用,可以从链表中间轻松添加、删除元素。

List<String> staff  = enw LinkList<>();
//添加三个员工
staff.add("anny");
staff.add("bob");
staff.add("candy")
Iterator iter = staff.iterator();
String first = iter.next();//访问第一个元素
String second = iter.next();//访问第二个元素
iter.remove(); //删除最后一个访问的元素

链表和泛型集合之间有一个重要区别:链表是有序结合(ordered collection),每个对象的位置很重要,LinkedList.add方法是将对象添加到链表尾部,有时需要将元素添加在链表中间。迭代器是描述集合中位置的,这种依赖位置的add方法有迭代器负责,只对有序集合使用迭代器有意义,(set是无序集合,无意义)在Iterator接口中没有add方法,集合类库提供了子接口ListIterator中包含add方法

interface ListIterator<E> extends Iterator<E>
{
	void add(E element);
	..
}

与Collection.add不同,这个方法不反悔boolen类型的值,它假定每次添加都会改变链表。另外,ListIterator接口有两个方法,可以用来反向遍历链表
E previous()
boolean hadPrevious()
与next方法一样,previous方法放回越过的对象。
LinkedList类的listIterator方法返回一个实现了ListIterator接口的迭代器对象。
ListIterator<String> iter = staff/listIterator();
add方法在迭代器位置之前添加一个新对象
例如:

List<String> staff  = enw LinkList<>();
staff.add("anny");
staff.add("bob");
staff.add("candy")
ListIterator<String> iter = staff.listIterator();
iter.next();//跳过第一个元素
iter.add("julia"); 

如果多次调用add方法,将按照提供的次序依次把元素添加到链表中,被依次添加到迭代器当前位置之前

set方法用一个新元素取代调用next或previous方法返回的上一个元素,例如,下面会用一个新值取代链表的第一个元素。

ListIterator<String> iter = list.listIterator();
String oldValue = iter.next();
iter.set(newValue);//将newvalue赋值给第一个元素

并发修改问题

如果某个 迭代器在修改集合时,另一个迭代器对其进行遍历,会出现混乱情况(一个迭代器指向一个刚刚删除的元素前面,现在这个迭代器就是无效的),为了避免发生并发修改的异常,可遵循:

  • 根据需要给容器附加多个迭代器,但是这些迭代器只能读取列表。
  • 另外再付一个既能读又能写的迭代器。

有一种方法可以检测到并发修改的问题,集合可以跟踪改写操作的次数,每一个迭代器都维护一个独立的计数值,在每个迭代器方法的开始处检查自己操作的计数值是否与集合的改写操作计数值一致,如果不一致抛出ConcurrentModificationException异常。

使用链表的好处就是尽可能减少在列表中插入或删除元素所付出的代价,如果列表中只有少数几个元素,完全可以是ArrayList。

避免使用正数索引表示链表中位置的所有方法,如果需要对集合进行随机访问,就是用数组或ArrayList,而不要使用链表

数组列表

List接口用于描述一个有序集合,并且集合中的每个元素的位置十分重要,有两种访问元素的方法:

  1. 使用迭代器
  2. 使用get、set方法随机访问每个元素

后者不适用与链表,但对数组很有用。

注意:在需要动态数组时,可能会使用Vector类。Vector类的所有方法都是同步的,可以由两个线程安全的访问一个Vector对象;
ArrayList是方法不同步的,因此在不需要同步时建议使用ArrayList,需要同步时使用Vector

散列集

如果需要查看指定元素的却又不知道元素位置,就需要访问所有元素,直到找到为止,如果集合中元素很多,就会消耗很多时间,可以有集中快速查找元素的数据结构,但缺点是无法控制元素出现的次序。
散列表(Hashtable) 可以快速找到所需元素,散列表会为每个对象计算一个整数,称为散列码(Hash code)散列码是由对象的实例域产生,不同数据域的对象会产生不同散列码。

Java中散列表用链表数组实现,每一个列表称为桶(bucket),想要查找表中对象的位置,就要先计算散列码,然后与桶的总数取余,得到的结果就是保存这个元素的桶的索引。例如:对象散列码为76268,有128个桶,那么对象应该保存在第108个桶中(76268除以128余108),如果幸运这个桶中没有其他元素,此时将元素直接插入桶中即可;如果桶占满,这种情况称为散列冲突(hash collision),这时需要用新对象与桶中的所有对象进行比较,查看这个对象是否已经存在,如果散列码是合理且随机分布的,桶的数目也足够大,需要比较的次数就会很少。散列表如果想要更多的控制散列表的运行性能,就要制定一个初始的桶值,桶数是用来收集具有相同散列值的桶的数目,如果要插入到散列表中的元素太多,就会增加冲突,降低性能。

  • 如果大致知道最终会有多少元素插入到散列表中,就设置桶数,一般为元素个数的75%~150%。
  • 如果不知道元素个数或最初估计太低导致散列表太满,可以再散列(rehashed),再散列需要建立一个桶数更多的表,然后将元素插入到新表中,然后丢弃原来的表。**装填因子(load factor)**决定何时对散列表再散列,如果装填因子设为0.75(默认值),表中超过75%的位置已经填入元素,这个表就会用双倍的桶自己进行再散列。

散列表可用于实现几个重要的数据结构

  • set类型,没有重复元素的元素集合,set的add方法首先在集中查到要添加的对象,不存在就添加进去。
  • HashSet类,它实现了基于散列表的集,用 add方法添加元素,contains方法被重新定义,用来快速查找某个元素是否已经出现在集中,只在某个桶中查到,不必查到集合中所有元素
  • 散列集迭代器将依次访问所有的桶,由于散列将元素分散在各个位置,所以访问的顺序几乎是随机的,只有不关心集合元素的顺序采用HashSet。

树集

TreeSet类是一个有序集合,可以以任意顺序将元素插入到集合中,对集合进行遍历时,每个值将自动的按照排列后的顺序呈现,顺序是用树结构完成的。
将一个元素添加到树中要比添加到散列集中慢,但是与检查数组或链表中重复元素相比,还是快得多。

映射

映射(map)用来存键值对,如果提供键就能查到值

基本操作

HashMap和TreeMap都实现了Map接口。

  • 散列映射对键进行散列,与键关联的值不能进行散列或比较。
  • 树映射用键的整体顺序对元素进行排序,并将其组织成搜索树。

散列相对快一点,如果不许按照顺序排序,最好选择散列。

建立

为员工建立散列映射

Map<String, Employee> staff = new HashMap<>();
Employee harry = new Employee("Harry Hacker");
staff.put("789-456-132", harry);

每当往映射中添加对象时,必须提供一个键,这里键是一个字符串,对应的值是employee对象

检索

get检索对象

String id = "789-456-132";
e = staff.get(id);

如果映射中没有给定与键对应的信息,get返回null,null返回值可能并不方便,最好有一个默认值,作为映射不存在的键

Map<String, Integer> scores = ...;
int score = scores.get(id, 0);//如果不对应返回0

键必须是唯一的,不能对同一个键存放两个值,如果对一个键调用两次put方法,第二次就会覆盖第一次的值。

删除

remove方法用于删除给定键对应的元素,size方法用于返回映射中的元素数。迭代映射最好使用forEach

staff.remove("789-456-132");//删除789-456-132对应的对象
//遍历
scores.forEach((k, v) ->
	System.out.println("key="+ k +", value = " + v));

更新映射项

正常情况下,可以得到与一个键关联的值,完成更新,再放回更新后的值,有一个特殊情况:键第一次出现,get会返回null,会出现一个NullPointerException异常,可以使用merge方法更新。例如使用映射统计一篇文章中word出现的次数

counts.merge(word, 1, Integer::sum);

将把word和1关联,否则使用Ingeger::sum函数组合原值和1。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值