、
一 、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慢。