Java基础 自学讲义 6.Java集合框架

Java集合框架这个东西应该是类似于C++的STL标准库, 便于使用一些常见的数据结构:D

目录
一.Java集合框架

  1. 将接口和实现分离
  2. Collection接口
  3. 迭代器
  4. 泛型使用方法
  5. 集合框架中的接口

二. 具体的集合

  1. 链表LinkedList(List)
  2. 数组列表ArrayList(List)
  3. 散列集HashSet(Set)
  4. 树集TreeSet(SortedSet)
  5. 队列与双端队列Queue&Deque
  6. 优先级队列PriorityQueue

三. 映射(Map)

  1. 基本映射操作
  2. 基本映射操作
  3. 基本映射操作

一.Java集合框架

CoreJava中提到C++的STL提供了一种泛型算法, 现在想来的确是这样的, 还是挺好用的, 嘤嘤嘤;
本文中有一些地方出现了容器, 就是Java中集合的概念, 是我自己习惯说容器了, 呜呜;

1.将接口和实现分离

不要直接在一个类里面实现一个集合, 可以用一个支持接口的类来实现集合, 比如要实现一个队列, 既可以使用链表LinkedLIstQueue, 也可以使用循环列表CircularArrayQueue, 只需要implements queue就可以用不同的类实现一个队列接口了, 在定义一个队列的时候就可以先这样写一个接口:

然后这样去定义队列了:

Queue<People> humanity = new CircularArrayQueue<>(100);

表示定义一个长度为100的队列, 用循环列表来实现;

2.Collection接口

在Java中集合类的基本接口是Collection, 它提供了2个基本方法:
一个是add, 表示向集合中加入一个元素, 如果加入成功了就返回true, 否则返回false;
一个是Iterator迭代器, 用于遍历集合中的元素;

3.迭代器

迭代器就是Iterator;

可以用Iterator的next方法来遍历容器, 如果没有next就会返回一个NoSuchElementException错误, 所以调用next之前要用hasNext检查;
恕在下直言这笨比方法希望我不要多用, 尽量用for each来直接遍历容器把;
for-each循环可以和任何实现了iterable接口的对象一起工作;
在有了lambda表达式之后可以用forEachRemaining方法配合lambda表达式一起对容器内的元素做相同的操作;
在这里插入图片描

java中的iterator和C++中有一个巨大的不同:

所以在调用it.remove的时候, 是删除了上一个访问过的元素, 所以如果想要删除下一个迭代器的元素, 要先用it.next访问, 然后再remove掉;
如果在使用it.remove之前没有使用it.next, 会报错IllegalStateException
这样测试一下iterator:

import java.util.*;

class test12 {
	public static void main(String[] args) {
		Collection<String> a = new Vector<String>();
		a.add("GodV");
		a.add("We1less");
		for(Iterator<String>it = a.iterator();it.hasNext();){
			System.out.println((String)it.next());
		}
		
	}
}

要记得最好不要太早定义iterator, 可以等到用的时候再定义, 因为一旦执行Iterator<String>it = a.iterator()就会自动把迭代器初始化到容器的第一个位置;

4. 泛型使用方法

在Collection接口中, Java提供了很多有用的方法, 列举一些如下:



在我的测试中遇到了一些问题, 先上我写的MeVector测试代码:

import java.util.*;
import java.lang.reflect.*;

class test12 {
	public static void main(String[] args) {
		MyVector<String> me = new MyVector<String>();
		me.add("GodV");
		me.add("We1less");
		System.out.println(me.size());
		System.out.println(me.contains("GodV"));
		for(Iterator<String> it =me.iterator();it.hasNext();){
			System.out.println(it.next());
		}
		AbstractCollection<String> s = new Vector<>();
		Class<?> cl = s.getClass();
		Method[] Methods = cl.getDeclaredMethods();
		for(Method i : Methods){
			System.out.println(i);
		}
	}
}

class MyVector<E> extends AbstractCollection<E>{
	private Vector<E> vec;
	
	public MyVector(){
		vec = new Vector<E>();
	}

	@Override
	public int size(){
		return vec.size();
	}
	@Override
	public Iterator<E> iterator(){
		Iterator<E> it = vec.iterator();
		return it;
	}
	public boolean add(E e){
		return vec.add(e);
	}
}

//output:
//2
//true
//GodV
//We1less
//public synchronized boolean java.util.Vector.add(java.lang.Object)
//private void java.util.Vector.add(java.lang.Object,java.lang.Object[],int)
//public void java.util.Vector.add(int,java.lang.Object)
//public synchronized java.lang.Object java.util.Vector.remove(int)
//public boolean java.util.Vector.remove(java.lang.Object)
//......省略一些
//void java.util.Vector.checkInvariants()

首先, 如果不在MyVector中重写add方法, 就会出现unsupportedoperationexception异常, 仔细寻找错误后, 我用反射找到了AbstractCollection类中的方法, 发现add方法的返回值是void, 去查询文档中的add方法是这样的:

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

怪不得会出现这样的异常, 所以我重写了add方法;
但是其他的一些方法, 比如contains是可以正常使用的;

5. 集合框架中的接口

上一张图表示框架继承层次:
集合框架继承层次
随机访问

二. 具体的集合


所有的类都实现了Collection接口, 而以map结尾的类实现了Map接口;

1.链表LinkedList

链表的实现细节就不多说了, 谈谈Java中具体的链表的特点;
Collection中的iterator有next,hasNext和remove等一些方法, 但是没有add方法, previous这些方法;
因为Collection.iterator很通用, 所有对于某些无序集合比如set这些, 所以不能有add和previous这些方法, 因为不是按照下表索引序来的, 是无序的, 所以没有这些方法;
但是List是有序的, 所以现在有了ListIterator这个新的迭代器, 仅用于List, 有add, previous, hasPrevious方法, 就可以按顺序访问, 增加链表中的值, 还有get方法和set方法, 可以获取和修改指定迭代器位置的值;
使用add方法, 新加入的方法会被依次添加到迭代器当前位置之前;
要注意使用previous方法的时候, remove方法的效果和之前的有所不同, remove会删除之前操作过的那个变量, 而不是删除光标前面的那个变量;

在使用多个迭代器的时候要特别注意, 如果两个迭代器同时操作了当前的链表, 那么另一个迭代器的顺序就会发生改变, 容易出现问题, 所以CoreJava中建议, 同时最多使用一个迭代器做既读又写的操作, 其余的迭代器都只做读的工作;
Collection也有toString方法, 返回一个类似于[A, B, C, …]这样的字符串;
使用get方法, 以随机index的方法获取某个元素是效率极低的, 在Java中已经对get做了优化, 如果索引大于 size()/2 就从列表尾端开始搜索元素;
比较高效的方法是nextIndex和previousIndex方法来获取previous和next的元素的索引;
由于链表的随机访问元素效率比较低, 但是增删元素效率比较高;
所以如果经常需要随机访问元素最好就使用数组或者ArrayList;
随手测试一下:

import java.util.*;

class test13 {
	public static void main(String[] args) {
		List<String> me = new LinkedList<String>();
		me.add("GodV");
		me.add("We1less");
		me.add("weishen");
		me.add("mifengaaa");
		ListIterator<String> aIterator = me.listIterator();
		while(aIterator.hasNext()){
			System.out.println((String)aIterator.next());
		}
		System.out.println();
		aIterator.previous();aIterator.previous();
		aIterator.remove();
		aIterator = me.listIterator();
		while(aIterator.hasNext()){
			System.out.println((String)aIterator.next());
		}
	}
}
//output:
//GodV
//We1less
//weishen
//mifengaaa
//
//GodV
//We1less
//mifengaaa
//可见,remove会删除上一个遍历过的元素, 而不是像光标一样删除前面一个元素

上一些总结的常用的List和LinkedList的方法:

2.数组列表ArrayList

ArrayList应该是一个和Vector差不多的东西, 主要特点是可以动态改变大小, 区别在于:
Vector支持线程的同步, 同一时刻只有一个线程能够写Vector, 避免多线程同时写而引起的不一致性, 但实现同步需要很高的花费,所以它比ArrayList慢;
这个东西的详细使用前面写过了, 然后前面关于List的一些方法他也能用;
在这个博客里:Java基础 自学讲义 2. OOP部分特性 二.C.泛型数组列表

3.散列集HashSet

散列集, 散列表, 哈希表啥的都是这个东西;
书上讲的有点皮, 有点没看懂, 用一个比较好的散列函数是比较重要的;
这里贴一篇我以前C++里学散列表的博客, 原理都是一样的;
散列表随手测试一下:

import java.util.*;

class test14 {
	public static void main(String[] args) {
		Set<String> me =new HashSet<>();
		me.add("ZedKing");
		me.add("GodV");
		me.add("Godv");
		me.add("mifengaaa");
		me.add("mifengaaa");
		System.out.println(me.size());
		for(Iterator<String>it=me.iterator();it.hasNext();){
			System.out.println(it.next());
		}
	}
}
//output
//4
//GodV
//Godv
//ZedKing
//mifengaaa

Set是对大小写敏感的;
HashSet的toArray方法返回的是一个Object[]对象, 所以不能用一个ArrayList去接收;
因为在HashSet建立之前, 如果能对他的大概容量和容量使用率能有一个比较准确的预估, 可以大大提升这个哈希表的效率, 所以构造方法提供了以下几种:

最后一种只有在散列表的填充百分比大于loadFactor的百分比时, 散列表才会进行再散列(rehashed);

4. 树集TreeSet

TreeSet是由红黑树实现的(Red-Black Tree), 可以实现有序集合SortedSet
相比于普通Set就是可以实现有一定的顺序;
先上一个我的TreeSet测试代码:

import java.util.*;
import java.lang.reflect.*;

class test15 {
	public static void main(String[] args) {	
		SortedSet<item> aSet = new TreeSet<item>();
		aSet.add(new item("GodV",13));
		aSet.add(new item("mifengaaa",30));
		aSet.add(new item("Aluka",123));
		aSet.add(new item("Cpt",33));
		aSet.add(new item("GodV",42));
		System.out.println(aSet);
		NavigableSet<item> bSet = new TreeSet<>(
			Comparator.comparingInt(p->p.getNumber())
		);
//		像下面这样写也可以:
//		NavigableSet<item> bSet = new TreeSet<>(
//			Comparator.comparingInt(item::getNumber)
//		);
		bSet.addAll(aSet);
		System.out.println(bSet);

	}
}

class item implements Comparable<item>{
	private int number;
	private String description;
	
	public item(){}
	public item(String description, int number){
		this.number = number;
		this.description = description;
	}
	public int getNumber(){
		return this.number;
	}
	public String getDescription(){
		return this.description;
	}
	@Override
	public int compareTo(item u){
		return this.description.compareTo(u.getDescription())==0?Integer.compare(this.number, u.getNumber()):this.description.compareTo(u.getDescription());
	}
	@Override
	public String toString(){
		return this.description+"-"+this.number;
	}
}

//output
//[Aluka-123, Cpt-33, GodV-13, GodV-42, mifengaaa-30]
//[GodV-13, mifengaaa-30, Cpt-33, GodV-42, Aluka-123]

上面的代码中, 第一部分是我按照item类implement的Comparable接口, 重写了compareTo方法, 按照description排序的, 次关键词number排序, 所以在TreeSet中是直接按照Comparable接口实现的compareTo的方法排序的;
第二部分我查看了TreeSet的Constructor

		TreeSet<item> a = new TreeSet<>();
		Class<?> cl = a.getClass();
		Constructor<?>[] methods = cl.getDeclaredConstructors();
		for(Constructor<?> i : methods)	System.out.println(i);	

发现有下面几种构造器:

public java.util.TreeSet(java.util.SortedSet)
public java.util.TreeSet(java.util.Collection)
public java.util.TreeSet(java.util.Comparator)
public java.util.TreeSet()
java.util.TreeSet(java.util.NavigableMap)

所以也可以使用lambda表达式或者使用method reference传入一个Comparator来实现排序的指定, 这样就不会默认去调用自己的compareTo方法了;
也可以传入一个SortedSet或者Collection来Copy所有的内容;
最后上一些树集常用的方法:

也可以在这里看到:
极客学院TreeSet
还有最后一个问题, 上面我们使用到了三个接口, TreeSet, SortedSet和 NavigableSet; 这三个接口主要是支持的方法有些不一样, 需要的时候酌情选用把…

5. 队列与双端队列

ArrayDeque和LinkedList都实现了deque;
要注意的是接口也对deque提供了push pop方法, 但是我觉得最好不要用, 因为容易分不清前面和后面, 最好用addFirst addLast offerFirst offerLast和removeFirst removeLast pollFirst pollLast, 比较好区分;
给一些常用的方法:

6. 优先级队列

Java中的优先队列是用堆(heap)实现的(最小堆, 小顶堆), 当然也可以用二叉搜索树实现;

堆和二叉搜索树的区别是:
堆一定是完全二叉树, 二叉搜索树不一定是完全二叉树

名字叫PriorityQueue
有一些方法, 比如poll offer peek这种 还有isEmpty这些, 用到自然就知道了…
还有一个好像更牛逼的东西叫PriorityBlockingQueue优先级阻塞队列, 可用于并发?

三.映射

Java里的映射就是map, 提供一种 键-值 的数据结构

1.基本映射操作

Map主要有两种, HashMap和TreeMap, 一个无序一个有序的, HashMap稍微快一点;
有put, get, remove, forEach这些常见的方法, 要注意的是遍历Map的方法, 我总结了4种, CoreJava中给出的是第一种使用forEach+lambda表达式, 我认为这种方法应该是最好的;
遍历Map:
第一种可以使用Map的forEach方法加上Java8的lambda表达式:

aMap.forEach( (k,v)->{System.out.println(k+" "+v);} );

第二种可以使用Map.Entry来遍历Map的条目:

for(Map.Entry<String, String> it : aMap.entrySet()){
	System.out.println(it);
}

第三种可以使用for结合Map的keySet和values方法来遍历:

for(String a : aMap.keySet()){
	System.out.println(a);
}
for(String a : aMap.values()){
	System.out.println(a);
}

第四种是使用迭代器, 这种是看起来比较熟悉而且效率挺高的, 但是要注意, 不能在使用for循环访问迭代器的同时使用remove操作, javadoc说这样会发生不可预期的错误, 如果希望迭代的同时删除元素, 可以使用while来遍历:

		for(Iterator<Map.Entry<String, String>> it = aMap.entrySet().iterator();it.hasNext();){
			System.out.println(it.next());
		}

当然还有第五种是在遍历keySet的时候调用get方法获取对应的值, 但是这种方法太捞了, 效率很低, 不提了, 就上一段测试代码吧:

for(String i : aMap.keySet()){
	System.out.println(i+"="+aMap.get(i));
}

测试代码如下:

import java.util.*;

class test16 {
	public static void main(String[] args) {
		Map<String,String> aMap = new TreeMap<>();
		aMap.put("Aluka", "AluWife");
		aMap.put("GodV", "mifengaaa");
		aMap.put("zz", "lym");
		aMap.forEach( (k,v)->{System.out.println(k+" "+v);} );
		aMap.remove("zz");
		System.out.println(aMap.getOrDefault("zz", "NoThisEntry"));
		for(Map.Entry<String, String> it : aMap.entrySet()){
			System.out.println(it);
		}
		for(String a : aMap.keySet()){
			System.out.println(a);
		}
		for(String a : aMap.values()){
			System.out.println(a);
		}
		for(Iterator<Map.Entry<String, String>> it = aMap.entrySet().iterator();it.hasNext();){
			System.out.println(it.next());
		}
	}
}

贴一些map和sortedmap的常用方法:

猜你喜欢

转载自blog.csdn.net/qq_33982232/article/details/83021274