稳稳当当学java之Set、泛型和Map(17)

第十九章 Set、泛型和Map

1. 作业回顾

1.从键盘随机输入10个整数,保存到List中,并按照倒序排序,从大到小的顺序显示出来。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

public class Day18HomeWork {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		
		Scanner s = new Scanner(System.in);
		for (int i = 0; i < 10; i++) {
			System.out.println("请输入第" + (i+1) +"个整数:");
			list.add(s.nextInt());
		}
		
		Collections.sort(list, new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				return o2.intValue() - o1.intValue();
			}
		});
		for (Integer i : list) {
			System.out.print(i + ", ");
		}
        s.close();
	}
}

2. LinkedHashSet

LinkedHashSet是HashSet的子类,并且其操作和HashSet相同。

LinkedHashSet不允许重复的元素,但是保留插入元素的顺序。

当对LinkedHashSet进行迭代时,它会按照元素的添加顺序来返回。

import java.util.LinkedHashSet;
import java.util.Set;

public class Day1902 {
	public static void main(String[] args) {
//		Set<String> set = new HashSet<String>();
		Set<String> set = new LinkedHashSet<String>();//保留添加元素的顺序
		
		set.add("张三");
		set.add("李四");
		set.add("王五");
		
		for (String str : set) {
			System.out.println(str);
		}
	}
}

3. TreeSet

TreeSet实现Set接口和SortedSet接口,其操作和HashSet相同。

TreeSet不允许重复元素,但是它将元素按照顺序来存储,此顺序不是插入元素的顺序,而是元素的大小顺序。当对TreeSet进行迭代时,它将按照元素从小到大的顺序来返回。

import java.util.TreeSet;

public class Day1903 {
	public static void main(String[] args) {
		TreeSet<Integer> set = new TreeSet<Integer>();
		
		set.add(10);
		set.add(20);
		set.add(30);
		set.add(15);
		set.add(5);
		
		for (Integer i : set) {
			System.out.println(i);
		}
		
		System.out.println();
		
		set.add(12);
		for (Integer i : set) {
			System.out.println(i);
		}
	}
}

TreeSet对元素进行排序时需要知道排序规则,有两种方式可以解决:

1,元素实现Comparable接口。

2,创建TreeSet时提供一个比较器。

import java.util.Comparator;
import java.util.TreeSet;

public class Day1903 {
	public static void main(String[] args) {
		//传递一个比较器,从大到小排序
		TreeSet<Integer> set = new TreeSet<Integer>(new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				return o2.intValue() - o1.intValue();
			}
			
		});
		
		set.add(10);
		set.add(20);
		set.add(30);
		set.add(15);
		set.add(5);
		
		for (Integer i : set) {
			System.out.println(i);
		}
		
		System.out.println();
		
		set.add(12);
		for (Integer i : set) {
			System.out.println(i);
		}
	}
}

4. 泛型

使用泛型创建的类和方法可以支持不同类型的数据。

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

//自定义的有序列表
class Mylist1{
	//Object表示values可以加入任何类型的元素
	private List<Object> values = new ArrayList<Object>();
	
	public void add(String str) {
		values.add(str);
	}
	
	public void remove(String str) {
		values.remove(str);
	}
}

//自定义的有序列表
class Mylist2{
	//Object表示values可以加入任何类型的元素
	private List<Object> values = new ArrayList<Object>();
	
	public void add(Integer i) {
		values.add(i);
	}
	
	public void remove(Integer i) {
		values.remove(i);
	}
}

//E是泛型参数
class Mylist3<E>{
//	private List<E> values = new ArrayList<E>();
	private List<Object> values = new ArrayList<Object>();
	
	public void add(E i) {
		values.add(i);
	}
	
	public void remove(E i) {
		values.remove(i);
	}
	
	public E get(int index) {
		return (E) values.get(index);
	}
	
}
public class Day1904 {
	public static void main(String[] args) {
		//泛型的类在使用前需要传递泛型参数
		Mylist3<String> list1 = new Mylist3<String>();
		list1.add("a");
		list1.add("b");
		list1.add("c");
		String str = list1.get(1);
		System.out.println(str);//b
		
		Mylist3<Integer> list2 = new Mylist3<Integer>();
		list2.add(1);
		list2.add(2);
		list2.add(3);
		
		Integer i = list2.get(1);
		System.out.println(i);//2
	}
}

在类名后的<>中声明的T代表任意类型,还可以使用M,K等等,但是一般都使用T(type),在创建类的对象时,使用具体的类型来代替T。

T可以作为类的成员变量,方法的参数和返回值的类型。

4.1泛型限定

在上面的例子中,T代表此类可以接受任何类型,可以使用泛型限定约束此类能接受的类型。

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

//E是泛型参数
//适用泛型类MyList4时,只能使用number的子类作为泛型参数
//使用E extends Number,表示此泛型只能使用Number的子类作为泛型参数
class Mylist4<E extends Number>{
//	private List<E> values = new ArrayList<E>();
	private List<Object> values = new ArrayList<Object>();
	
	public void add(E i) {
		values.add(i);
	}
	
	public void remove(E i) {
		values.remove(i);
	}
	
	public E get(int index) {
		return (E) values.get(index);
	}
	
}

public class Day1905 {
	public static void main(String[] args) {
		//不能接受String作为泛型参数
//		Mylist4<String> list1 = new Mylist4<String>();//编译错误
		
		Mylist4<Integer> list2 = new Mylist4<Integer>();
		Mylist4<Float> list3 = new Mylist4<Float>();
		Mylist4<Byte> list4 = new Mylist4<Byte>();
		
	}
}

4.2泛型通配符

收编

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

public class Day1906 {
	public static void main(String[] args) {
		List<Number> list = new ArrayList<Number>();
		
		List<Integer> list1 = new ArrayList<Integer>();
		list1.add(1);
		list1.add(2);
		list1.add(3);
		
		List<Double> list2 = new ArrayList<Double>();
		list2.add(1.0);
		list2.add(2.0);
		list2.add(3.0);
		
		List<String> list3 = new ArrayList<String>();
		list3.add("a");
		list3.add("b");
		list3.add("c");
		
		//将list1中的所有元素加入到list中
		list.addAll(list1);
		//将list2中的所有元素加入到list中
		list.addAll(list2);
		//将list3中的所有元素加入到list中
//		list.addAll(list3);//编译错误
		
		for (Number str : list) {
			System.out.println(str);
		}
	}
}

4.3创建泛型方法

import java.util.Date;

public class Day1907 {
	//泛型方法,<T>是泛型参数声明
	public static <T> void f1(T t) {
		System.out.println(t);
	}
	
	//泛型方法,<T>是泛型参数声明,返回值类型是T
	public static <T> T f2(T t) {
		System.out.println(t);
		return t;
	}
	
	//泛型方法,<T extends Number>是泛型参数声明,返回值类型是T
	public static <T extends Number> void f3(T t) {
		System.out.println(t);
	}
	
	public static void main(String[] args) {
		f1(1);
		f1("a");
		f1(new Date());
		
		System.out.println(f2('a'));
		
		f3(1.0);
//		f3("abc");//编译错误
	}
}

5. Map接口

Map代表了一个映射表,用于保存键值对。map不能包含重复的key,但是value可以重复。每个key只能对应一个value。

Map示意图:

在这里插入图片描述
Map接口API:

import java.util.Collection;
import java.util.Set;

public interface Map<K,V> { 
	//获取映射表中键值对的数量 
	int size(); 
	//判断映射表是否为空 
	boolean isEmpty(); 
	//判断映射表是否包含指定的key 
	boolean containsKey(Object key); 
	//判断映射表是否包含指定的value 
	boolean containsValue(Object value); 
	//通过key获取value 
	V get(Object key); 
	//put不仅可以添加,还可以修改,如果是修改,那么会返回旧value 
	V put(K key, V value); 
	//删除指定key所属的Entry 
	V remove(Object key); 
	//清空映射表 
	void clear(); 
	//将全部的key作为一个set集合返回 
	Set<K> keySet(); 
	//将全部的value作为一个collection集合返回 
	Collection<V> values(); 
	//将全部的entry作为一个set集合返回
	Set<Map.Entry<K, V>> entrySet(); 
}

6. HashMap

6.1HashMap介绍

HashMap实现了Map接口,HashMap是无序的。

它不保存元素的插入顺序,在内部也不保证元素按照大小顺序排列。

import java.util.HashMap;
import java.util.Map;

class Student{
	private String name;
	private int age;
	
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public int getAge() {
		return age;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}	
}

public class Day1908 {
	public static void main(String[] args) {
		//使用String作为key的类型,Student作为value的类型
		Map<String,Student> map = new HashMap<String, Student>();
		
		//放入三个entry
		map.put("20191101", new Student("张三", 20));
		map.put("20191102", new Student("李四", 21));
		map.put("20191103", new Student("王五", 22));
		
		//通过键获取对应的值
		System.out.println(map.get("20191102"));
		
		//如果key存在,新的value将会替代旧的value
		map.put("20191102", new Student("马六", 22));//Student [name=李四, age=21]
		
		//通过键获取对应的值
		System.out.println(map.get("20191102"));//Student [name=马六, age=22]
		
		System.out.println(map);//HashMap重写了toString方法	
	}
}

HashMap不允许重复的key,它通过key的equals方法来判断。因此作为HashMap的key必须重写equals方法,同时也需要重写hashCode方法。

6.2HashMap的遍历

HashMap的遍历,HashMap没有实现iterable接口,因此不能使用foreach进行遍历。但是它的keySet方法可以返回其key的集合,可以实现遍历。

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Day1909 {
	public static void main(String[] args) {
		Map<String,String> map = new HashMap<String, String>();
		map.put("a", "A");
		map.put("b", "B");
		map.put("c", "C");
		
		Set<String> keys = map.keySet();
		
		for (String key : keys) {
			System.out.println("key:" + key + ",value:" + map.get(key));
		}
		
		System.out.println();
		
		Set<Map.Entry<String,String>> entrys = map.entrySet();
		
		for (Map.Entry<String,String> entry : entrys) {
			System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
		}
	}
}

7. LinkedHashMap

LinkedHashMap是HashMap的子类,并且其操作和HashMap相同。

HashMap是无序的,不保留元素的插入顺序,LinkedHashMap保留元素的插入顺序。当对LinkedHashMap进行迭代时,它将按照元素添加的顺序返回。

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class Day1910 {
	public static void main(String[] args) {
//		Map<String, String> map = new HashMap<String, String>();
		Map<String,String> map = new LinkedHashMap<String, String>();
		map.put("a", "A");
		map.put("c", "C");
		map.put("b", "B");
		
		Set<String> keys = map.keySet();
		
		for (String key : keys) {
			System.out.println("key:" + key + ",value:" + map.get(key));
		}
	}
}

8. TreeMap

TreeMap实现了Map接口和SortedMap接口,其操作和HashMap相同。

TreeMap是有序的。TreeMap内部将按照元素的key的大小顺序排序。

TreeMap和TreeSet一样,要求key实现了Comparable接口,或者在构造TreeMap时提供一个Comparator的实例。

8.1 key实现Comparable接口

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class Day1911 {
	public static void main(String[] args) {
		Map<Integer, String> map = new LinkedHashMap<Integer, String>();
		map.put(3, "张三");
		map.put(2, "李四");
		map.put(1, "王五");
		
		Set<Integer> keys = map.keySet();
		
		for (Integer key : keys) {
			System.out.println("key:" + key + ",value:" + map.get(key));
		}
	}
}

8.2 提供一个Comparator的实例

import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class Day1911 {
	public static void main(String[] args) {
		Map<Integer, String> map = new TreeMap<Integer, String>(new Comparator<Integer>() {
		
			public int compare(Integer o1, Integer o2) {
				return o2.intValue() - o1.intValue();//从大到小排序
			}
		});
		
		map.put(1, "王五");
		map.put(3, "张三");
		map.put(2, "李四");
		
		Set<Integer> keys = map.keySet();
		
		for (Integer key : keys) {
			System.out.println("key:" + key + ",value:" + map.get(key));
		}
	}
}

9. Queue接口

Queue为队列,队列是一种先进先出(FIFO)的数据结构。
在这里插入图片描述
Queue的实现类LinkedList

import java.util.LinkedList;
import java.util.Queue;

public class Day1912 {
	public static void main(String[] args) {
		Queue<String> q = new LinkedList<String>();
		
		//将元素插入队列
		q.offer("a");
		q.offer("b");
		q.offer("c");
		System.out.println(q);//[a, b, c]
		
		//获取但不移除队列的头
		System.out.println(q.peek());//a
		System.out.println(q);//[a, b, c]
		
		//获取并移除队列的头
		System.out.println(q.poll());//a
		System.out.println(q);//[b, c]
		
		System.out.println(q.poll());//b
		System.out.println(q.poll());//c
		System.out.println(q);//[]	
	}
}

10. Vector

Vector实现了List接口,内部由数组实现。但是与ArrayList不同的是,Vector是线程安全的,可以在多线程环境中使用。但同时也要注意其效率低于ArrayList。

import java.util.List;
import java.util.Vector;

public class Day1913 {
	public static void main(String[] args) {
		List<String> vector = new Vector<String>();
		vector.add("a");
		vector.add("b");
		vector.add("c");
		System.out.println(vector);//[a, b, c]
	}
}

11. HashTable

HashTable实现了Map接口,其操作和HashMap类似。但是HashTable是线程安全的。但是要注意其效率低于HashMap。

import java.util.Hashtable;

public class Day1914 {
	public static void main(String[] args) {
		Hashtable<Integer, String> map = new Hashtable<Integer, String>();
		map.put(1, "a");
		map.put(2, "b");
		map.put(3, "c");
		System.out.println(map);//{3=c, 2=b, 1=a}
	}
}

12. HashMap原理

12.1 数组,链表,散列表的区别

链表和数组可以按照人们的意愿排列元素的次序。但是,如果想要査看某个指定的元素, 却又忘记了它的位置, 就需要访问所有元素, 直到找到为止。如果集合中包含的元素很多,将会消耗很多时间。如果不在意元素的顺序, 可以使用能够快速査找元素的数据结构:散列表。

12.2 散列表原理

散列表是存放entry的集合,每个entry包含一个key和一个value。散列表为每一个key计算一个整数, 称为散列码(hashcode)。 散列码是由key的数据域产生的一个整数。 具有不同数据域的key将产生不同的散列码。

在Java 中, 散列表用链表数组实现。数组中的每个元素被称为桶( bucket) 。

例如, 如果某个entry的key的散列码为15745, 并且有128 个桶, entry应该保存在第1 号桶中( 15745除以128 余1)。或许会很幸运, 在这个桶中没有其他元素, 此时将元素直接插人到桶中就可以了。

当然,有时候会遇到桶被占满的情况,这也是不可避免的,这种情况称为散列冲突,这时需要用新entry的key与桶中所有的entry的key进行比较,查看这个对象是否已经存在。如果存在,那么新的value将替换旧的value。如果不存在,那么就将新的entry插入到链表的末尾。

如果知道entry的key,需要在散列表中查询其value,那么首先需要计算其key值的散列码,然后对桶的总数取余,得到对应桶的索引位置。如果该索引处只有一个元素,那么就调用key的hashCode方法和equals方法进行对比。如果对比通过就返回其value,否则认为未找到。如果该索引处不止一个元素,而是一个链表,那么依次对此链表所有元 素的key调用hashCode方法和equals方法进行比较。
在这里插入图片描述

12.3 hashCode和equals方法

通过散列表的原理,我们不难发现,要想保证散列表正常使用。key必须保证其实现了正确的hashCode和equals方法。Object类自带HashCode和equals方法,因此其所有的子类都具备这两个方法。但是这个两个方法的实现在通常情况下并不合理。HashCode方法应该能使key在散列表上均匀分布,并且有效避免散列冲突。equals方法也不能只比较对象的内存地址,应该根据实际的情况来实现。幸运的是,如果我们使用String来作为key的类型,那么就不用关心这个两个方法的实现,因为String类重写了Object类的这两个方法。

12.4 再散列

如果在创建的时候不指定散列表的桶数,那么它将使用默认值是16(2的4次幂)。如果散列表太满,就需要进行再散列。如果要对散列表再散列,就需要创建一个桶数更多的表,并将所有元素插入到这个新表中,丢弃原来的表。装填因子(load factor)决定何时对散列表进行再散列。例如,如果装填因子是0.75(默认值),就是说如果表中超过了75%的位置已填入元素,这个表就会用双倍的桶数进行再散列。

如果预先知道需要放入多少个元素,最好再创建散列表时就指定其初始容量(initial capacity),已防止其自动的再散列。但是散列表的容量总是2的n次幂,因为这样能使散列分布的更加均匀并且提高散列表的查询效率。

14. 练习

key调用hashCode方法和equals方法进行比较。

12.3 hashCode和equals方法

通过散列表的原理,我们不难发现,要想保证散列表正常使用。key必须保证其实现了正确的hashCode和equals方法。Object类自带HashCode和equals方法,因此其所有的子类都具备这两个方法。但是这个两个方法的实现在通常情况下并不合理。HashCode方法应该能使key在散列表上均匀分布,并且有效避免散列冲突。equals方法也不能只比较对象的内存地址,应该根据实际的情况来实现。幸运的是,如果我们使用String来作为key的类型,那么就不用关心这个两个方法的实现,因为String类重写了Object类的这两个方法。

12.4 再散列

如果在创建的时候不指定散列表的桶数,那么它将使用默认值是16(2的4次幂)。如果散列表太满,就需要进行再散列。如果要对散列表再散列,就需要创建一个桶数更多的表,并将所有元素插入到这个新表中,丢弃原来的表。装填因子(load factor)决定何时对散列表进行再散列。例如,如果装填因子是0.75(默认值),就是说如果表中超过了75%的位置已填入元素,这个表就会用双倍的桶数进行再散列。

如果预先知道需要放入多少个元素,最好再创建散列表时就指定其初始容量(initial capacity),已防止其自动的再散列。但是散列表的容量总是2的n次幂,因为这样能使散列分布的更加均匀并且提高散列表的查询效率。

14. 练习

1,编写程序,在main方法中接受5个整数,创建TreeSet类型的集合,将5个整数添加到集合中,使用foreach遍历该集合。提供一个比较器,然后对于集合中的整数进行从大到小的顺序进行排序。然后使用Iterator进行迭代。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十年之伴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值