第一讲 Set

一 概述

Set:1. 元素存储无下标,所以元素是无序(存入和取出的顺序不一定一致)

  2. 元素不可以重复

|--HashSet:底层数据结构是哈希表。线程不同步。 保证元素唯一性的原理:判断元素的hashCode值是否相同。如果相同,还会继续判断元素的equals方法,是否为true。

|--TreeSet:可以对Set集合中的元素进行排序。默认按照字母的自然排序。底层数据结构是二叉树。保证元素唯一性的依据:compareTo方法return 0。       

Set集合没有特有的功能,Set集合的功能和Collection是一致的

二 HashSet

HashSet:线程不安全,存取速度快。

HashSet的原理: HashSet中不能存入重复的值,如果add()存入重复值,返回false,那么HashSet如何保证数据是不是唯一的呢?

如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数

可以通过元素的两个方法,hashCode和equals来完成保证元素唯一性。如果元素的HashCode值相同,才会判断equals是否为true。如果元素的hashCode值不同,不会调用equals。

2. 知道了HashSet原理之后所以我们在存储自定义类的时候必须重写hashCode()和equals()方法

* hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)

* equals(): 属性相同返回true, 属性不同返回false,返回false的时候存储 

3. HashSet扩展,如果遇到这样一种情况,既需要保证元素的唯一性,但是同样需要保证元素的存取顺序,那么就可以用HashSet的子类—LinkedHashSet  链表HashSet

例子1:往hashSet集合中存入自定对象 姓名和年龄相同为同一个人,重复元素。要去除重复元素

/* 
往hashSet集合中存入自定对象 
姓名和年龄相同为同一个人,重复元素。去除重复元素 
思路:1、对人描述,将人的一些属性等封装进对象 
      2、定义一个HashSet容器,存储人对象 
      3、取出 
 
*/  
import java.util.*;  
  
//人描述  
class Person  
{  
    private String name;  
    private int age;  
  
    Person(String name,int age)  
    {  
        this.name=name;  
        this.age=age;  
    }  
  
    public String getName()  
    {  
        return name;  
    }  
  
    public int getAge()  
    {  
        return age;  
    }  
  
    public boolean equals(Object obj)  
    {  
        if(!(obj instanceof Person))  
            return false;  
        Person p=(Person)obj;  
        return this.name.equals(p.name)&&this.age==p.age;  
    }  
  
    public int hashCode()  
    {  
        return this.name.hashCode()+this.age;  
    }  
}  
class  HashSetTest  
{  
    public static void main(String[] args)   
    {  
        HashSet h=new HashSet();  
        h.add(new Person("shenm",10));  
        h.add(new Person("shenm2",6));  
        h.add(new Person("shenm1",30));  
        h.add(new Person("shenm0",10));  
        h.add(new Person("shenm0",10));  
          
        getOut(h);  
  
    }  
  
    //取出元素  
    public static void getOut(HashSet h)  
    {  
        for (Iterator it=h.iterator(); it.hasNext(); )  
        {  
            Person p=(Person)it.next();  
            sop(p.getName()+"..."+p.getAge());  
        }  
    }  
  
    //打印  
    public static void sop(Object obj)  
    {  
        System.out.println(obj);  
    }  
}  

例子2:使用Scanner从键盘读取一行输入,去掉其中重复字符, 打印出不同的那些字符

   Scanner sc = new Scanner(System.in);   //创建键盘录入对象
  System.out.println("请输入一行字符串:");
  String line = sc.nextLine();     //将键盘录入的字符串存储在line中
  char[] arr = line.toCharArray();    //将字符串转换成字符数组
  LinkedHashSet<Character> hs = new LinkedHashSet<>();
  
  for(char c : arr) {        //遍历字符数组
   hs.add(c);         //将字符数组中的字符添加到集合中
  }
  
  for (Character ch : hs) {      //遍历集合
   System.out.print(ch);
  }

 

三 TreeSet

1. 特点

a) 底层的存储数据结构为二叉树结构

b) 存储在TreeSet集合中的元素会进行排序,我们可以指定一个顺序, 对象存入之后会按照指定的顺序排列

1. 这同时也意味着存入TreeSet中元素必须是同一种类型(如果一个存Integer,一个存String就会报java.lang.ClassCastException异常)

2. 也意味着我们在创建TreeSet的同时,必须为排序指定顺序(下面讲解1.自然顺序2.选择器),如果没有指定那么同样会报异常

小知识点:8种基本数据类型的封装类都默认实现了Comparable接口,可以直接比较

c) 从排序方式我们可以具体知道TreeSet二叉树存储的原理

2. 为TreeSet指定排序顺序

① 第一种排序方式:自然排序

让元素自身具备比较性。元素需要实现Comparable接口,覆盖compareTo方法。这种方式也被称为元素的自然顺序,或者叫做默认顺序。

示例:在TreeSey集合中插入一些学生对象(姓名和年龄),按年龄从小到大进行排序,如果年龄相同按姓名的字典序排序

步骤1 : 定义学生类,继承Comparable接口,重写compareTo()方法

	static class Student implements Comparable<Student> {
		String name;
		int age;

		public Student(String name, int age) {
			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 name + "...." + age;
		}

		@Override
		public boolean equals(Object obj) {
			if(!(obj instanceof Student))  {
				return false;
			}
	        Student s = (Student)obj;  
	        return this.name.equals(s.name)&&this.age==s.age;  
		}

		/**
		 * 复写compareTo以便TreeSet集合调用  
		 * 按年龄从小到大排序,如果年龄一样按名字的字典序排列
		 */
		@Override
		public int compareTo(Student o) {
			int num = this.age - o .getAge();
			return num == 0 ? this.name.compareTo(o.getName()):num;
		}
	}

ComPareTo()方法中返回值类型代表在二叉树的左右方向,this代表刚加入的方法,Student o为已经存储在二叉树结构中对象

如果返回值 1. 返回值大于0,this在二叉树的右边  2. 如果返回值小于0,this在二叉树的左边 3. 如果返回值等于0,代表this与Student o一致,直接抛弃不存储

而TreeSet读取的时候根据二叉树的前序遍历分别将元素取出

步骤2: 加入元素,查看结果,分析结果

TreeSet<Student> set = new TreeSet<>();
  set.add(new Student("张三", 23));
  set.add(new Student("李四", 22));
  set.add(new Student("王五", 36));
  set.add(new Student("周六", 12));
  System.out.println(set);

结果为:[周六....12, 李四....22, 张三....23, 王五....36] 

分析结果:略

② 第二种排序方式: 选择器

在集合初始化时,定义一个比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。 格式如下:

TreeSet<Integer> ts = new TreeSet<>(new Comparator<Integer>() {//将比较器传给TreeSet的构造方法 
    @Override
    public int compare(Integer i1, Integer i2) {
      // 比较器逻辑     

    }
});

当两种排序都存在时,以比较器为主。

上面的例子也可以这样写

TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
   @Override
   public int compare(Student o1, Student o2) {
    int num = o1.getAge() -o2.getAge();
    return num == 0? o1.getName().compareTo(o2.getName()):num;
   }
  });
  set.add(new Student("张三", 23));
  set.add(new Student("李四", 22));
  set.add(new Student("王五", 36));
  set.add(new Student("周六", 12));
  System.out.println(set);

结果:[周六....12, 李四....22, 张三....23, 王五....36]

注意:调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数

③ 两种排序方式的区别:

* TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)

* TreeSet如果传入Comparator, 就优先按照Comparator

 

第四讲   Map 

 

一 概述

   1. 简述

 ① Map<K,V>集合是一个接口,和List集合及Set集合不同的是,它是双列集合,并且可以给对象加上名字,即键(Key),然后将键映射到值的对象

 ② 一个映射不能包含重复的键,键可以存储null

 ③ 每个键最多只能映射到一个值

  2. HashSet与HashMap

   HashSet底层使用HashMap实现的,HashSet的键—值对的键用来存储值,所以能保证唯一,而值是自定义的一个object

  3. Map体系(子类)

 |--HashMap:底层是哈希表数据结构。允许使用null键null值,该集合是不同步的。JDK1.2,效率高。

 |--TreeMap:底层是二叉树数据结构。线程不同步。可以用于给Map集合中的键进行排序。 

 

 

二 Map集合常用的方法

1. 添加 

V put(K key,V value):添加元素。

* 如果键是第一次存储,就直接存储元素,返回null

* 如果键不是第一次存在,就用值把以前的值替换掉,返回以前的值

2. 删除

* void clear():移除所有的键值对元素

* V remove(Object key):根据键删除键值对元素,并把值返回

3. 判断

* boolean containsKey(Object key):判断集合是否包含指定的键

* boolean containsValue(Object value):判断集合是否包含指定的值

* boolean isEmpty():判断集合是否为空

4. 获取

*Collection<V> values():获取集合中所有值的集合

* int size():返回集合中的键值对的个数

* Set<Map.Entry<K,V>> entrySet():

* V get(Object key):根据键获取值

* Set<K> keySet():获取集合中所有键的集合

 

三 Map集合的两种迭代方式

1. 键找值原理: 将Map集合转成Set集合。再通过Set集合的迭代器取出各个键

  ① 通过Set<K> keySet():将Map中所以的键存入到Set集合 

  ② Set具备迭代器。所以可以通过迭代方式取出所以键的值,

  ③ 再通过Map集合的get方法。获取每一个键对应的值。

HashMap<String, Integer> hm = new HashMap<>();
   hm.put("张三", 23);
   hm.put("李四", 24);
   hm.put("王五", 25);
   hm.put("赵六", 26);
   
   /*Set<String> keySet = hm.keySet();   //获取集合中所有的键
   Iterator<String> it = keySet.iterator(); //获取迭代器
   while(it.hasNext()) {      //判断单列集合中是否有元素
      tring key = it.next();     //获取集合中的每一个元素,其实就是双列集合中的键
      Integer value = hm.get(key);   //根据键获取值
      System.out.println(key + "=" + value); //打印键值对
   }*/
   
   for(String key : hm.keySet()) {    //增强for循环迭代双列集合第一种方式
    System.out.println(key + "=" + hm.get(key));
   }

2. 键值对对象(Map.Entry<String, Integer>)找键和值思路

① 获取所有键值对对象的集合 hm.entrySet()

② 遍历键值对对象的集合,获取到每一个键值对对象 Entry<String, Integer> en = it.next();

③ 根据键值对对象找键和值     String key = en.getKey();    Integer value = en.getValue();

其实,Entry也是一个接口,它是Map接口中的一个内部接口。

 

  1. nterface Map  
  2.     {  
  3.          public static interface Entry  
  4.          {  
  5.               public abstract Object getKey();  
  6.               public abstract Object getValue();  
  7.          }  
  8.  }  

 

代码实现: 

  HashMap<String, Integer> hm = new HashMap<>();
   hm.put("张三", 23);
   hm.put("李四", 24);
   hm.put("王五", 25);
   hm.put("赵六", 26);
   /*Set<Map.Entry<String, Integer>> entrySet = hm.entrySet(); //获取所有的键值对象的集合
   Iterator<Entry<String, Integer>> it = entrySet.iterator();//获取迭代器
   while(it.hasNext()) {
      Entry<String, Integer> en = it.next();    //获取键值对对象
      String key = en.getKey();        //根据键值对对象获取键
      Integer value = en.getValue();       //根据键值对对象获取值
      System.out.println(key + "=" + value);
   }*/
   
   for(Entry<String,Integer> en : hm.entrySet()) {
      ystem.out.println(en.getKey() + "=" + en.getValue());
   }

 

关于Map.Entry:

        Map是一个接口,其实,Entry也是一个接口,它是Map的子接口中的一个内部接口,就相当于是类中有内部类一样。为何要定义在其内部呢?

        原因:a、Map集合中村的是映射关系这样的两个数据,是先有Map这个集合,才可有映射关系的存在,而且此类关系是集合的内部事务。

                b、并且这个映射关系可以直接访问Map集合中的内部成员,所以定义在内部。

 因为HashMap和TreeMap与Set接口下的HashSet和TreeSet一样(只是将Map键值对中的键当作Set中的值),就不做介绍了,下面来看几个例子

四 例子解析

  何时使用Map集合:当量数据之间存在着映射关系的时候,就应该想到使用Map集合。

  1. 统计字符串中每个字符出现的次数

String str = "aaaabbbbcccccddddd";
        char[] arr = str.toCharArray();
        HashMap<Character, Integer> hm = new HashMap<>();
        for(char c: arr){ // 循环遍历数组,将字符出现情况传入HashMap中
            if(!hm.containsKey(c)){
                hm.put(c, 1);
            }else{
                hm.put(c, hm.get(c)+1);
            }
        }
        
        for (Character key : hm.keySet()) {                    //遍历双列集合
            System.out.println(key + "=" + hm.get(key));
        }

 2. HashMap和Hashtable的区别 

共同点:底层都是采用Hash算法实现,都是双列集合

不同点: 1.HashTable线程安全,需要加同步判断锁,所以效率低,HashMap是JDK1.2版本出现的,是线程不安全的,效率高

     2.Hashtable不可以存储null键和null值,HashMap可以存储null键和null值

3. Collections工具类

与Arrays数组工具类类似,集合也由有自己的工具类—Collections,包含一些集合的方法

常用方法:

①  public static <T> void sort(List<T> list)  对List集合进行排序

②  public static <T> int binarySearch(List<?> list,T key) List集合二分法查找

③  public static <T> T max(Collection<?> coll)  集合中查找最大值

④  public static void reverse(List<?> list)  对List集合进行反转

⑤  public static void shuffle(List<?> list)  对集合进行随机排序

4. 模拟斗地主洗牌和发牌

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;



/**
 * 用Map集合和List集合模拟斗地主洗牌和发牌
 * @author Administrator
 *
 */
public class doudizhu {
    
    /*
     * 包含四个步骤
     * 1. 构建一副牌
     * 2. 洗牌
     * 3. 发牌
     * 4. 看牌
     */
    public static void main(String[] args){
        // 1. 构建一副牌
        String[] num = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"}; 
        String[] color = {"方片","梅花","红桃","黑桃"};
        HashMap<Integer,String> hm = new HashMap<>(); // 代表每张牌,存储索引和牌
        ArrayList<Integer> list = new ArrayList<>();  // 存储索引,每一个索引对应HashMap中的一张牌
        int index = 0 ;
        for(String s1:num){
            for(String s2:color){
                hm.put(index, s2.concat(s1));   //将索引和扑克牌添加到HashMap中
                list.add(index);                //添加索引
                index++;
            }
        }
        hm.put(index, "小王");
        list.add(index++);
        hm.put(index, "大王");
        list.add(index);
        // 2. 洗牌
        Collections.shuffle(list);
        // 3. 发牌(三个人和三张的底牌)
        TreeSet<Integer> zhangsan = new TreeSet<>();   // 用TreeSet(有序)存储张三的牌
        TreeSet<Integer> lisi = new TreeSet<>();       // 用TreeSet(有序)存储李四的牌
        TreeSet<Integer> wangwu = new TreeSet<>();     // 用TreeSet(有序)存储王五的牌
        TreeSet<Integer> dipai = new TreeSet<>();      // 用TreeSet(有序)存储底牌
        for(int i=0 ;i<list.size();i++){ // 开始发牌
            if(i >= list.size() - 3) {
                dipai.add(list.get(i));                         //将list集合中的索引添加到TreeSet集合中会自动排序
            }else if(i % 3 == 0) {
                zhangsan.add(list.get(i));
            }else if(i % 3 == 1) {
                lisi.add(list.get(i));
            }else {
                wangwu.add(list.get(i));
            }
        }
        // 4 . 看牌
        lookPoker("张三", zhangsan, hm);
        lookPoker("李四", lisi, hm);
        lookPoker("王五", wangwu, hm);
        lookPoker("底牌", dipai, hm);
        
    }
    
    public static void lookPoker(String name,TreeSet<Integer> ts,HashMap<Integer, String> hm) {
        System.out.print(name + "的牌是:");
        for (Integer index : ts) {
            System.out.print(hm.get(index) + " ");
        }
        System.out.println();
    }
}