黑马程序员——Java之集合框架(二)

------------------- Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ---------------------

一 、Map集合


Map与List、Set接口不同,该集合存储键值对,一对一对往里存,而且要保证健的唯一性。同时它也没有继承Collection。实现map的有:HashMap、TreeMap、

HashTable、Properties、EnumMap。

注意:set集合底层就是使用了map集合。

Map集合特点:




二、Map常见操作


1、添加

V put(K key, V value)存入一对键值对。如果添加相同的健时,新的值会覆盖原有健的值。并会返回被覆盖的值。

void putAll(Map<? extends K,? extendsV> m)把一个集合添加到现集合中。

2、删除

void clear() 清空集合中的所有元素。

V remove(Object key)  删除集合中的健,返回键所对应的值。如果没有该健则返回null。

3、判断

boolean containsKey(Object key)判断健是否存在。

boolean containsValue(Object value)判断值是否存在。

boolean isEmpty() 判断该集合是否为健值对,

4、获取

V get(Object key)获取某一个健所对应的值。

int size()获取长度

Collection<V> values() 返回所有的值。

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

Set<K> keySet() 将键存入到Set集合中。

5、map遍历

map集合有俩种取出方式。

将map中所有的健存入到set集合中,因为set具备迭代器,所以可以迭代方式取出所有的健,再根据get方法,获取每一个健对应的值。

(1)方式一:Keyset

public class MapDemo {
     public static void main(String[] args) {
         Map<Integer,String> m= new HashMap<Integer,String>();
         m.put(101, "zhangsan1" );
         m.put(102, "zhangsan2" );
         m.put(103, "zhangsan3" );
         m.put(104, null );
         m.put( null,"zhangsan5" );
         Set<Integer> keyset=m.keySet(); //获取Map中所有的键存入set集合中。
        for (Iterator<Integer> it=keyset.iterator();it.hasNext();){
         Integer key=it.next(); //通过set遍历set集合中存入的键。
         String value=m.get(key); //通过取出的健通过Map的get方法获取其对应的值。
         System. err .println(key+"=" +value);
        }
    }
}

(2)entryset方式

public class EntrysetDemo {
    public static void main(String[] args) {
         Map<Integer,String> map= new HashMap<Integer,String>();
         map.put(101, "zhangsan1");
         map.put(102, "zhangsan2");
         map.put(103, "zhangsan3");
         map.put(104, "zhangsan4");
         Set<Map.Entry<Integer, String>> set=map.entrySet();//将Map中的映射关系存入到set集合中。
          for(Iterator<Map.Entry<Integer, String>>it=set.iterator();it.hasNext();){
              Map.Entry<Integer, String>me=it.next();//取出set集合中的映射关系。
              Integer key=me.getKey(); //通过映射关系,获取键。
              String value=me.getValue(); //通过映射关系,获取值。
              System. err.println(key+value);
         }
    }
}

注意:

Map.Entry其实Entry也是一个接口,它是Map接口中的一个内部接口。没有映射


三、Map集合子类


1、HashMap

(1)内部存储结构

    java中数据存储方式最底层的两种结构,一种是数组,另一种就是链表,数组的特点:连续空间,寻址迅速,但是在删除或者添加元素的时候需要有较大幅度的移动,所以查

询速度快,增删较慢。而链表正好相反,由于空间不连续,寻址困难,增删元素只需修改指针,所以查询慢、增删快。有没有一种数据结构来综合一下数组和链表,以便发挥他

们各自的优势?答案是肯定的!就是:哈希表。哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法

采用“拉链法”,我们可以理解为“链表的数组”,如下图:



从上图中,我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一

般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、

108以及140都存储在数组下标为12的位置。它的内部其实是用一个Entity数组来实现的,属性有key、value、next。接下来我会从初始化阶段详细的讲解HashMap的内部结

构。

(1)练习一
描述学生,定义map容器,将学生作为键,地址作为值存进去,学生姓名和年龄不能重复,获取map集合的元素。
public class Students implementsComparable<Students>  {
     private String name;
     private int age ;
    Students(String name, int age){
          this .name =name;
          this .age =age;
    }
     public void setName(String name){
          this .name =name;
    }
     public String getName(){
          return name ;
    }
     public void setAge(int age){
          this .age =age;
    }
     public int getAge(){
          return age ;
    }
     //将数据存储到哈希表的集合中,需要覆盖hashcode和equals方法;
     public int hashCode(){
          return name .hashCode()+ age*25;
    }
     public boolean equales(Object obj){
          if (!(obj instanceof Students))
               throw new ClassCastException("类型不匹配");
         Students s=(Students)obj;
          return this .name .equals(s. name) && this .age ==s.age ;
    }
     //如果要存储到二叉树数据结构的集合中需要让数据具备比较性,否则存入第二条数据会报错。
     public int compareTo(Students s){
          int num= new Integer( this. age).compareTo( new Integer(s.age ));
          if (num==0){
               return this .name .compareTo(s.name);
         }
          return num;
    }
     public String  toString(){
          return name +age ;
    }
}
 
public class HashMapTest {
     public static void main(String[] args) {
         HashMap<Students,String> hm= new HashMap<Students,String>();
         hm.put( new Students("张三" ,20), "北京" );
         hm.put( new Students("李四" ,21), "山东" );
         hm.put( new Students("王五" ,23), "上海" );
         hm.put( new Students("马六" ,18), "石家庄" );
          //取出第一种方式: keyset
         Set<Students> set=hm.keySet();
          for (Iterator<Students> it=set.iterator();it.hasNext();){
              Students s=it.next();
              System. err.print(s+hm.get(s)+"..." );
         }
          //第二种方式: entryset
         Set<Map.Entry<Students,String>> entry=hm.entrySet();
          for (Iterator<Map.Entry<Students,String>>iter=entry.iterator();iter.hasNext();){
              Map.Entry<Students,String>me=iter.next();
              Students key=me.getKey();
              String value=me.getValue();
              System. err .println(key+value);
         }
    }
}


2、HashTable

HashTable和HashMap采用相同的存储机制,二者的实现基本一致,不同的是:

1、HashMap是非线程安全的,HashTable是线程安全的,内部的方法基本都是synchronized。

2、HashTable不允许有null值的存在。

在HashTable中调用put方法时,如果key为null,直接抛出NullPointerException。其它细微的差别还有,比如初始化Entry数组的大小等等,但基本思想和HashMap一样。

3、因为同步所以性能比HashMap要低


3、TreeMap

键以某种排序规则排序,内部以red-black(红-黑)树数据结构实现,实现了SortedMap接口

<pre name="code" class="java">//TreeeMap练习:年龄排序
//学生类
class Student implements Comparable<Student>
{
	private String name;
	private int age;
	Student(String name,int age){
		this.name=name;
		this.age=age;
	}
	public void setName(String name){
		this.name=name;
	}
	public String getName(){
		return name;
	}
	public void setAge(int age){
		this.age=age;
	}
	public int getAge(){
		return age;
	}
	public int hashCode(){
		return name.hashCode()+age*15;
	}
	public boolean equals(Object obj){
		if(!(obj instanceof Student))
			throw new RuntimeException("不是学生");
		Student s=(Student)obj;
		return this.name.equals(s.name) && this.age==s.age;
	}
    //如果要存储到二叉树数据结构的集合中需要让数据具备比较性,否则存入第二条数据会报错。
	//按年龄排序
	public int compareTo(Student s){
		int num=new Integer(this.age).compareTo(new Integer(s.age));
		if(num==0)
			return this.name.compareTo(s.name);
		return num;
	}
}
//定义比较器,按姓名长度排序,相同则按年龄排序。
class MyCompara implements Comparator<Student>
{
	public int compare(Student s1,Student s2){
		int num=new Integer( s1.getName().length()).compareTo(new Integer(s2.getName().length()));
		if(num==0)
			return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
        return num;
	}
}
class Test3
{
	public static void main(String[] args){
		TreeMap<Student,String> ts=new TreeMap<Student,String>(new MyCompara());
		ts.put(new Student("张三1",30),"北京");
		ts.put(new Student("李四11",70),"上海");
		ts.put(new Student("王五111",50),"北京");
		ts.put(new Student("马六11",40),"广州");
		ts.put(new Student("张三1",20),"北京");
		Set<Map.Entry<Student,String>> set=ts.entrySet();
		for(Iterator<Map.Entry<Student,String>> it=set.iterator();it.hasNext();){
			Map.Entry<Student,String> me=it.next();
			Student s=me.getKey();
			String key1=s.getName();
			Integer key2=s.getAge();
			String value=me.getValue();
			System.out.println("姓名:"+key1+" 年龄:"+key2+" 居之地:"+value);
		}
	}
}
 

运行结果:

姓名:张三1 年龄:20 居之地:北京
姓名:张三1 年龄:30 居之地:北京
姓名:马六11 年龄:40 居之地:广州
姓名:李四11 年龄:70 居之地:上海
姓名:王五111 年龄:50 居之地:北京


练习2 获取字符串字母出现的次数。希望打印结果a(1)b(2)....

分析:每个字母都有对应的次数,说明字母和次数之间有映射关系。那么就可以选择Map集合。

思路:

(1)将字符串转换成字符数组,因为要对每一个字母进行操作。

(2)遍历字符数组,将每一个字母作为键去查Map集合。如果返回null,将字符和1存入到Map集合中,如果返回的不是null,说明字母在Map集合中,覆盖原来键所对应的值,那么就获取该次数并进行自增,然后将该字母和自增后的次数存入到Map集合中。覆盖掉原来的值。

(3)将Map集合中的数据变成指定的字符串形式返回。

public class TreeMapTest1 {
     public static StringBuilder charcount(String str){
          //1.将字符串转换成字符数组。
          char [] ch=str.toCharArray();
          //2.定义TreeMap集合。
         TreeMap<Character,Integer> tm= newTreeMap<Character,Integer>();
          int count=0;//放在外面节省内存空间。
          for (int x=0;x<ch. length;x++){
               if (!(ch[x]>'a' &&ch[x]< 'z' || ch[x]> 'A' && ch[x]<'Z' ))//判断是否为字母。
                    continue ;//重新循环。
              Integer value=tm.get(ch[x]); //查找集合中字母值。
               //如果不为空则获取集合中的值进行加1再重新存入。如果为空,则不获取值自动加1,存入。
               if (!(value== null))
                   count=value;
              count++;
                   tm.put(ch[x], count);
                   count=0; //最后次数要重新清0。
//                 如果为空则将字母作为键字母的次数作为值存入进去。
//            if(value==null){
//                 tm.put( ch[x], 1);
//            }
//            else{
//                 value=value+1;//如果不为空则代表字母已经有了次数,需要在原有的次数上加1,然后重新存入。
//                 tm.put( ch[x], value);
//            }
         }
         StringBuilder sb=entry (tm);//定义输出方式。
          return sb;
    }
     public static StringBuilder entry(TreeMap<Character,Integer> tm){
         Set<Map.Entry<Character, Integer>> set=tm.entrySet();
         StringBuilder sb= new StringBuilder();
          for (Iterator<Map.Entry<Character, Integer>> it=set.iterator();it.hasNext();){
             Map.Entry<Character,Integer> me=it.next();
              Character key=me.getKey();
              Integer value=me.getValue();
              sb.append(key+ "("+value+")" );//将键和值都存入到字符串缓冲区中。
         }
          return sb;
    }
     public static void main(String[] args) {
          // TODO Auto-generated method stub
         String str= "aaabb_+-*+bcc-/*cdddeeefff"" ;
         StringBuilder sb= charcount(str);
         System. err .println(sb.toString());
    }
}

输出结果:

b(3)c(3)d(3)e(3)f(3)


4、ConcurrentHashMap

ConcurrentHashMap是线程安全的HashMap的实现。同样是线程安全的类,它与HashTable在同步方面有什么不同呢?

synchronized关键字加锁的原理,其实是对对象加锁,不论你是在方法前加synchronized还是语句块前加,锁住的都是对象整体,但是ConcurrentHashMap的同步机制和这个

不同,它不是加synchronized关键字,而是基于lock操作的,这样的目的是保证同步的时候,锁住的不是整个对象。

 

 

四、Map一对多映射。


在很多项目中,应用比较多的是一对多的映射关系,这就可以通过嵌套的形式将多个映射定义到一个大的集合中,并将大的集合分级处理,形成一个体系。

(1)每个公司都有多个部门,每个部门下面都有多个人员;

/**Map扩展:一对多映射。
* 练习一:公司里有N多部门,部门里有N员工。
* 练习二:学生在公司不同部门里实习,按照年龄排序(自然排序)和按照姓名长度排序(自定义排序)。
*/
public class Test3
{
	//练习一:公司里有N多部门,部门里有N员工。
	public static void method_1(){
		HashMap<String,TreeMap<String,Integer>> hs=new HashMap<String,TreeMap<String,Integer>>();
		TreeMap<String,Integer> xietong=new TreeMap<String,Integer>();
		TreeMap<String,Integer> yunwei=new TreeMap<String,Integer>();
		hs.put("协同项目部",xietong);
		hs.put("运维部",yunwei);

		xietong.put("张三",30);
		xietong.put("李四",40);
		yunwei.put("王五",50);
		yunwei.put("马六",60);
		Set<Map.Entry<String,TreeMap<String,Integer>>> set=hs.entrySet();
		for(Iterator<Map.Entry<String,TreeMap<String,Integer>>> it=set.iterator();it.hasNext();){
			Map.Entry<String,TreeMap<String,Integer>> me=it.next();
			String key=me.getKey();
			TreeMap<String,Integer> tm=me.getValue();
			System.out.println("部门:"+key);
			keySet(tm);	
		}
	}
	//遍历TreeMap集合
	public static void keySet(TreeMap<String,Integer> tm){
		Set<String> set=tm.keySet();
		for(Iterator<String> it=set.iterator();it.hasNext();){
			String key=it.next();
			Integer value=tm.get(key);
			System.out.println("姓名:"+key+" 年龄:"+value);
		}
	}
	//练习二:学生在公司不同部门里实习,按照年龄排序(自然排序)和按照姓名长度排序,长度相同则按照年龄排序(自定义排序)。
	public static void method_2(){
		HashMap<String,Set<Student>> hm=new HashMap<String,Set<Student>>();
		Set<Student> xietong=new TreeSet<Student>(new MyCompara());
		Set<Student> yunwei=new TreeSet<Student>(new MyCompara());
		hm.put("协同项目部",xietong);
		hm.put("运维部",yunwei);

		xietong.add(new Student("张三",60));
		xietong.add(new Student("张三丰",40));
		xietong.add(new Student("李四",50));
		xietong.add(new Student("李四四四",40));
		xietong.add(new Student("张三",60));
		yunwei.add(new Student("王",50));
		yunwei.add(new Student("王五",70));

		Set<String> set=hm.keySet();
		for(Iterator<String> it=set.iterator();it.hasNext();){
			String key=it.next();
			System.out.println("部门:"+key);
			Set<Student> value=hm.get(key);
			iterator(value);
		}
	}
	//遍历set集合
	public static void iterator(Set<Student> ts){
		for(Iterator<Student> it=ts.iterator();it.hasNext();){
			Student s=it.next();
			String name=s.getName();
			int age=s.getAge();
			System.out.println("姓名:"+name+" 年龄"+age);
		}
	}
	public static void main(String[] args){
		method_2();
	}
}
//定义学生类
class Student implements Comparable
{
	private String name;
	private int age;
	Student(String name,int age){
		this.name=name;
		this.age=age;
	}
	public void setName(String name){
		this.name=name;
	}
	public String getName(){
		return name;
	}
	public void setAge(int age){
		this.age=age;
	}
	public int getAge(){
		return age;
	}
	//HashSet时,需重写hashCode和equals方法。
	public int hashCode(){
		return name.hashCode()+age*15;
	}
	public boolean equals(Object obj){
		if(!(obj instanceof Student))
			throw new RuntimeException("不是学生对象");
		Student s=(Student)obj;
		return this.name.equals(s.name)&&this.age==s.age;
	}
	//自然排序重写compareTo方法。
	public int compareTo(Object obj){
		if(!(obj instanceof Student))
			throw new RuntimeException("类型错误");
		Student s=(Student)obj;
		int num=new Integer(this.age).compareTo(new Integer(s.age));
		if(num==0)
			return this.name.compareTo(s.name);
		return num;
	}
}
//自定义比较器,按照名字长度排序。
class MyCompara implements Comparator<Student>
{
	public int compare(Student s1,Student s2){
		int num=new Integer(s1.getName().length()).compareTo(new Integer(s2.getName().length()));
		if(num==0)
			return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
		return num;
	}
}

五 总结:集合间区别


1、Vector和ArrayList

(1)vector是线程同步且线程安全的,而arraylist是线程异步不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。

(2)如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数

据,用vector有一定的优势。

(3)如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,这个时候使用vector和arraylist都可以。而如果移动一个指定位置的数据,这个时候就应该考虑到使用linklist。

(4) ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等

内存操作,所以索引数据快插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需

要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快!


2、ArrayList和LinkedList

(1)ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

(2)对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

(3)对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

注意:这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList.因为

ArrayList每插入一条数据,要移动插入点及之后的所有数据。


3、HashMap和TreeMap

(1)HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap,HashMap

中元素的排列顺序是不固定的。

(2)在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义

了hashCode()和 equals()的实现。这个TreeMap没有调优选项,因为该树总处于平衡状态。


4、hashTable和TreeMap

(1)历史原因:

Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现。

(2)同步性:

Hashtable是线程同步且安全的,而HashMap是线程异步不安全的 。

(3)值:

只有HashMap可以让你将空值作为一个表的条目的key或value 。

六、总结:集合的选择

1、List集合选择

(1)对于随机查询与迭代遍历操作,数组比所有的容器都要快。所以在随机访问中一般使用ArrayList

(2)LinkedList使用双向链表对元素的增加和删除提供了非常好的支持,而ArrayList执行增加和删除元素需要进行元素位移。

(3)对于Vector而已,我们一般都是避免使用。

(4)将ArrayList当做首选,毕竟对于集合元素而已我们都是进行遍历,只有当程序的性能因为List的频繁插入和删除而降低时,再考虑LinkedList。


2、Set集合选择

(1)HashSet由于使用HashCode实现,所以在某种程度上来说它的性能永远比TreeSet要好,尤其是进行增加和查找操作。

(2)虽然TreeSet没有HashSet性能好,但是由于它可以维持元素的排序,所以它还是存在用武之地的。


3、Map集合选择

(1)HashMap与HashSet同样,支持快速查询。虽然HashTable速度的速度也不慢,但是在HashMap面前还是稍微慢了些,所以HashMap在查询方面可以取代HashTable。

(2)由于TreeMap需要维持内部元素的顺序,所以它通常要比HashMap和HashTable慢。




 

------------------- Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ---------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值