注:因为协议原因,文中很多图片都是无法上传,文中提供了测试所用的源代码,读者可以自行进行测试,也能发现其中的奥妙。
Java中util包提供了一些集合类,这些集合类又被称为容器,主要有Map(映射)、List(表)、Queue(队列)和Set(集),Queue接口、List接口和Set接口继承了Collection接口,他们两个分别自己的两个实现类,HashSet和TreeSet , ArrayList和LinkedList。Queue接口(队列)存在一个子接口Deque(双向队列),Deque接口由两个实现类,分别是ArrayList类和ArrayDeque类
这里也就是说ArrayList实现了两个Collection的子接口,Queue(队列)、List(表)
Map存在两个子类,HashMap和TreeMap
其中只有Map集合接口不是Collection接口的子接口
Queue有一个子接口,Deque,该接口存实现类ArrayDeque和LinkedList
Set 集 集合中的对象不按特定的方式排序,并且没有重复的元素,Set 的 有的实现类能实现特定顺序的排序
List 列表 集合中的位置按照索引的位置排序,可以有重复的对象,允许按照对象在集合中的索引位置检索对象
Queue 队列 集合中的对象按照“先进先出"的规则进行排序,可以有重复的对象,只能在队列的尾部添加对象,在头部删减对象。
Map 映射 集合中的每个元素包含一对键key和值 value 对象,集合中没有重复的键对象,值对象可以重复。
Collection接口有三个子接口,分别Queue 队列、List 列表、Set 集合 ,而且Collection接口还实现了两个接口,分别Iterator接口和Iterable接口。
其中List 接口有两个实现类,ArrayList类和LinkedList类,还有一个历史集合类,Vector类,该类是线程安全的,并且该类还有一个子类Stack,该类同样是线程安全的。
Set 接口有两个实现类 HashSet 类和 TreeSet类 ,还有一个历史集合类Hashtable类,该类的功能和HashMap的功能大致是类似的,但是不同的是,Hashtable同样是线程安全的, TreeSet接口右承接了SortedSet接口 HashSet 类 还有一个子类 LinkedHashSet
Queue 接口有一个子接口 Deque Deque有两个实现类Linkedlist 和ArrayDeque
1.Collection接口 和 Iterator(迭代器) 接口
提供了很多方法,添加元素、删除元素和管理元素的方法。
Collection接口中的方法,
增加对象 add,
清空集合 clean
是否包含对象 contains
返回一个迭代器 iterator
删除对象 remove
返回集合长度 size
返回一个包含包括集合中所有元素的数组 toArray
Iterator 迭代器 所提供的方法
判断是否存在下一个对象 hasNext
返回 下一个对象 next
删除下一个对象 remove
使用Iterator迭代器遍历集合的时候,每次调用next方法,返回这个对象的值,并且将指针指向下一个变量和这个变量之间
Iterator接口的方法和输入/输出流的read方法是等效的,每一次调用都会消耗掉这个量
在Set集合中,因为对象是无序的,所有采用迭代器输出时,顺序是随机的
program one 假如某集合率先产生一个迭代器 iterator ,然后集合中的对象发生变换,会发生什么?
此时会抛出异常,因为其中一个迭代器对集合进行变换操作的时候,另一个迭代器就不可用了。
当我们采用多个迭代器对一个集合进行操作的时候,我们可以用某些迭代器只能采用查看不能修改集合数据,只有一个迭代器对集合既可以修改又可以查看,这是一种多线程对程序操作的思想。
progra two 集合只能存放对象,假设向里面添加基本数据类型的数据 会怎么样?
这样会涉及到一个新的问题,自动装箱和自动拆箱问题,假如我们我们想把基本数据类型放进集合中,就只能采用集合的保证类,int的保证类 也就是Integer ,要基本数据类型转为包装类,才能放进集合中。
在JDK5以后,为了简化代码,Java采用的自动装箱和自动拆箱的机制,也就是说在这个过程中,int类型的数据会自动转化为Integer类型对象然后放进集合中,这个过程叫做自动装箱,同样Integer对象取出来当作int类型时,也会自动将Integer类型转为自动int数据类型,这个过程叫做自动拆箱,不止在这里会存在自动装箱和自动拆箱的问题,在任何包装类和基本数据类型转换的过程都存在自动装箱和自动拆箱的过程。
program three
基本数据类型首位数字是0 ,表示的是8进制数字,0x为16进制 0b为二进制
program four Map接口的get方法,当没有对应的键值时,返回null
Map<String,Integer>map=newHashMap<>();
map.put("cc",4006);
map.put("hsy",4049);
map.put("jb",4007);
System.out.println(map.get("bj"));
program five 两个对象相等他们的哈希码一定相等吗?
当两个对象 == 时 ,表示他们引用的是同一个对象,所以他们的哈希码一定相等,因为Object类中的equips方法是和“==” 相同的,
publicbooleanequips(Objectobject){
returnthis==object;
}
所以Object的equip方法同样代表哈希码相同,但是有些方法覆盖了equips方法,所以不一定了
programer six 自然排序是什么?
自然排序是采用Comparable接口的,没有实现的Comparator接口的类是无法进行自然排序的,会抛出异常
而客户类排序就是该类要实现Compartor接口的CompareTo方法,初始化集合的集合的时候,选择该类,那么集合排序方式就是按照初始化时候所选的类中CompareTo方法排序的
迭代器方法中remove方法和next方法的关系
remove方法会调用上次next时返回的元素,也就是说每一个remove方法都要在next方法后面,要不然会抛出IllegalStateException异常
List<String>list=newLinkedList<>();
list.add("a");
list.add("b");
list.add("c");
Iteratoriterator=list.iterator();
while(iterator.hasNext())
iterator.remove();// 抛出异常
这样就是没有问题的
List<String>list=newLinkedList<>();
list.add("a");
list.add("b");
list.add("c");
Iteratoriterator=list.iterator();
while(iterator.hasNext()) {
iterator.next();// 这样就不会抛出异常
iterator.remove();
}
这两个方法具有依赖性
Java迭代器想处于两个元素之间的一个位置,在集合中是一个特殊的存在
ArrayList集合在查找元素时,有一个微小的优化,
List<String>list=newLinkedList<>();
list.add("a");
list.add("b");
list.add("c");
list.get(2);// 当查找的位置大于list.size()/2,后从后面开始遍历。
ListIterator接口
该接口时Iterator 的子接口,ArrayList类是通过ListIterator接口实现iterator接口的
该接口中的方法有listIterator(),返回一个listIterator迭代器
program seven 哈希表hashtable 散列集 是如何存储数据的
散列表为每个对象计算一个整数,这个整数称为散列码(hashcode),散列码是由对象的实例域产生的一个整数,更准确地说,具有不同数据域的对象产生不同的散列码,每个对象的散列码 由该类中的hashcode方法产生,一般来说,散列码和对象是一一对应的,所以我们在定义类的时候要重写这个类hashcode方法和equips方法,这样的话,假如两个对象相等,那么他们的散列码也是相等的
散列表采用链表数组实现,每个列表称为桶,bucket,要想查找表中对象的位置,就要先计算他的散列码,然后于桶的总数取余,就是保存这个数据的桶的索引。然后再从相应的桶中找出自己的对象
如果桶的数量足够多,每个桶中的对象数目很少,操作很少就可以找到你的对象,假如桶是满的,你要向里面插入对象,这是就会出现散列冲突,如果桶的数量是被规定的,那么桶中的存储结构就会从链表变成二叉树。在我们正常用散列表的时候,我们就要合适地设置桶的数量,数量过多,会占用非常大的内存空间,数量比较小的话,就发生散列冲突影响性能。
我们一般把桶的数目设置为将要插入元素数目的75%~150%,有些研究人员认为桶的数目是素数最好,因为这样能防止键的集聚;
我们还可以有另一种设置方法就是设置装填因子(负载因子),该因子为一个常数,是元素的数目和桶的数目的比值,一般设为0.75,假如设置了负载因子(装填因子),当桶的检测到它内部的状态因子达到0.75时,就会自动的扩充一倍桶的数目
Hash系列的 都是采用这种存储结构,所以我们在定义HashMap、HashSet、Hashtable都是这样定义的
2.List 列表
主要包括List接口和该接口的所有实现类
1.List接口继承了Collection接口和所有Collection接口中的所有方法,
List接口中包含Collection接口中的所有方法,除此之外,List接口还定义了get(int index)方法、set(int index,Object obj)方法和add(int index , Object obj)方法等等,有兴趣可以看API
List<Integer>list=newArrayList<>();
list.add(2);
list.add(3);
list.add(9);
Collections.sort(list);
list.set(2,10);
for(inti=0;i<list.size();i++)
System.out.println(list.get(i));
Collection<String>list=newArrayList<>();
list.add("a");
list.add("d");
list.add("c");
for(Stringi:list)
System.out.println(i);
// System.out.println(list.get(0)); 错误 因为Collection实例没有继承List接口,没有get方法
// 这里的list是Collection接口下的,所有没有get方法和set方法
List<Integer>list=newArrayList<>();
list.add(2);
list.add(3);
list.add(9);
Collections.sort(list);
list.set(2,10);
for(inti=0;i<list.size();i++)
System.out.println(list.get(i));
我们把Collection的实例换成List接口的实例,就发现是可以的
publicstaticvoidmain(String[]args) {
List<String>list=newArrayList<>();
list.add("a");
list.add("d");
list.add("c");
for(Stringi:list)
System.out.println(i);
System.out.println(list.get(0));// 这样就是可以的
2.List接口实现类有ArrayList和LinkedList
ArrayList实现了可变数组,与数组一样都是顺序存储结构,运行包含所有元素 ,包括null,存储有序的,所有可以根据位置索引,插入元素,所以插入、删除对象比较慢,因为要设计后续数据的操作。但是访问速度较快,因为是顺序存储结构。
Arraylist实现类,可以直接采用 Collections类进行排序
List<Integer>list=newArrayList<>();
list.add(newInteger(1));
list.add(newInteger(6));
list.add(newInteger(4));
list.add(2);
list.add(3);
list.add(9);
Collections.sort(list);
for(Integerinteger:list)
System.out.println(integer);
数组类Arrays类中asList方法,可以将一个数组保证成为List对象
String[]strings={"a","b","c"};
List<String>list=Arrays.asList(strings);
for(inti=0;i<list.size();i++){
System.out.println(list.get(i));
}
LinkedList采用链表结构保存对象,不是顺序存储结构,与链表类似,该数据结构查找数据较慢,因为和链表相同,找一个数据要从头往下翻,但是该结构的插入和删除操作速度较快。LinkedList存储数据开销更大,因为它不仅要存储数据还有存储节点。
这两种list的实现类所包含的方法基本都是一致的,而且List集合可以存储重复元素,下面的Set集合就是不可以存储重复元素的,List集合是有序的,Set集合是无序的。
ListIterator接口 ,只有List接口继承了Iterator接口,Map接口、Queue接口、Set接口不是ListIterator 的子接口,所以不能采用ListIterator接口中的方法
该接口实现的方法有
add 添加元素
hasPrevious 判断是否含有上一个元素
hasNext 判断是否含有下一个元素
previous返回上一个元素
next 返回下一个元素
ListIterator接口从后往前遍历的方式
List<Integer>list=newArrayList<>();
list.add(6);
list.add(8);
list.add(22);
list.add(18);
ListIteratorlistIterator=list.listIterator();
while(listIterator.hasPrevious())
System.out.println(listIterator.next());
这段代码是没有任何输出的,因为我们在list接口得到的迭代器,是在最前面的,相当于在第0个元素到第-1个元素之间,这样显然前面是没有元素的,也就是说hasprovies的方法返回值是false,所示是不可能打印出来的,所以只要将迭代器放在后面的位置就可以遍历出集合了,我们可以来试一下
List<Integer>list=newArrayList<>();
list.add(6);
list.add(8);
list.add(22);
list.add(18);
ListIteratorlistIterator=list.listIterator();
System.out.println("第一遍正序遍历");
while(listIterator.hasNext())
System.out.println(listIterator.next());
System.out.println("迭代器放到相应位置后,开始反遍历");
while(listIterator.hasPrevious())
System.out.println(listIterator.previous());
这样是遍历成功的,那么我们要创建两个迭代器呢!当然是不可以的,因为每一个迭代器创建时候的位置都是在前面的。可以试一试。
其中remove方法依赖于provies方法和next方法,也就是说会删除上面前一个provies方法返回的值或者前一个next方法返回的值
List<String> list = new LinkedList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
ListIterator iterator = list.listIterator();
System.out.println("第一个返回的值"+iterator.next()); //该值应该为1,然后跳到2所在的位置
System.out.println("第三个返回的值"+iterator.next()); // 同样该值应该跳到3 所在的位置
System.out.println("第三个返回的值"+iterator.previous());
// System.out.println(iterator.next());
iterator.remove();
for (String str:list
) {
System.out.println(str);
}
我们发现迭代器的remove方法总是会删除前一个provies方法或者前一个next方法返回的值
我们可以对Java中的数组和Arraylist集合和LinkedList来进行测试分别测试插入元素,查找元素的速度
我们发现随机访问和遍历操作这两个过程,Java数组是最快的,其次是Arraylist
Linkedlist在插入和删除元素这两个操作上是最快的,
Vector的各个方面的速度都是较慢,属于历史集合类了
LinkedList类中有
addFirst
addLast
removefirst
removeLast
getfirst
getlast方法
3.Set集合
Set集合中的对象至少简单的放进集合中,相当于打包了,所以Set集合不能存放重复对象,所以在操作时,要小心变换对象的重复现象
Set接口的实现类有HashSet和TreeSet
1.HashSet类实现了Set接口,有哈希表支持,该类不保证Set的迭代顺序,也就是说不保证数据的顺序不会发生变换。该类允许存放null
HashSet采用了哈希算法,所以存取速度比较快,查找速度也比较快
Set<String>set=newHashSet<>();
Stringstr1=newString("hsy");
Strings2=str1;
// 因为这里s2和str1 引用 的是同一个对象,而Set集合不允许放相同的对象,所以第二个对象就不会放进去
// 这里我们可以采用 String s2 = new String("hsy"); 这种形式创建对象,结果是一样的
Strings3=newString("四b");
set.add(str1);
set.add(s2);
set.add(s3);
for(Stringstring:set)
System.out.println(string);
前面我们看到了Set集合不能存放重复的对象,而对象相不相同是通过对象的equips方法判断的,而且还会判断的哈希码,所以比较两个Object对象时,这两个对象必须是一个地址的引用,才不能放进同一个集合中,否则是可以放进Set集合中的。
Set<Object>set1=newHashSet<>();
// 创建两个空的Object对象 应该是完全相同的 但是他们的引用时不同的,所以还是可以放进Set中
Objects1=newObject();
Objects2=newObject();
// 这里判断他们相不相等,是通过他们的引用来判断的
//因为他们的引用时不同的,所以可以放进同一个集合中
set1.add(s1);
set1.add(s2);
System.out.println(set1.size());// 输出 2
Set<Object>set1=newHashSet<>();
Objects1=newObject();
Objects2=s1;// 这样判断他们是相等的,因为他们是同一个引入
// 这里涉及到创建对象的问题
set1.add(s1);
set1.add(s2);
System.out.println(set1.size());// 输出 1
}
但是在String对象时,并不是这样的,因为String对象重写了Object类的equips方法和hashcode方法,所以这时就需要比较的两个的对象内容是否相同了
Set<String>set1=newHashSet<>();
Strings1=newString("a");
Strings2=newString("a");
// 这里就不会发生上面发生的事情了,因为这里String对象改写了equips方法,判断两对象是否相等时,不仅判断他们的引用,还要判断他们内容
set1.add(s1);
set1.add(s2);
System.out.println(set1.size());// 输出 1
在Object类中的(hashcode)哈希码是和位置相对应的,两个对象的哈希码,他们就是同一个引用,假如两个对象的哈希码不同,那么他们就不是同一个对象
在一些类会重写hashcode方法,拿String类来演示一下
Set<String>set=newHashSet<>();
Strings1=newString("s1");
Strings2=newString("s1");
//这两个对象的内容很明显是相同的,但是他们肯定不是同一个对象,那么这样可以放进同一个Set集合里面吗?
set.add(s1);
set.add(s2);
for(Stringstr:set)
System.out.println(str);
很明显是放不进去的,因为String类重新了Object的hashcode方法和equips方法
我们再来做一个演示
Set<food>set=newHashSet<>();// 这里 我们先提前创建一个food类,该类里面没有重写hashcode的方法和equips方法,还采用的是Object类的方法
foodf1=newfood("蔬菜","20027");
foodf2=newfood("蔬菜","20027");
set.add(f1);
set.add(f2);
for(foodstr:set)
System.out.println(str);
我们可以看到这两个对象的所对应的数据都是相同的,但还是放进去了,因为在这个类中没写重新equips方法和hashcode方法,所以才会这样的,而上面String类是重新了这个方法,所以不能放进相同的。
我们在food类里重写一下这两个方法试试看
publicbooleanequals(Objecto) {
if(this==o)returntrue;
if(o==null||getClass()!=o.getClass())returnfalse;
foodfood=(food)o;
returnObjects.equals(name,food.name)&&
Objects.equals(num,food.num);
}
@Override
publicinthashCode() {
returnObjects.hash(name,num);
结果就变成了这样的
2.TreeSet类不仅实现了Set接口,而且还实现了SortedSet接口,因此TreeSet实现的Set集合在遍历集合时按照自然顺序递增顺序,也可以按照指定的比较器递增排序。
TreeSet排序的方法时采用的红黑树排序,这种排序非常快,将五万个数据方便放到HashSet中和TreeSet中,你会发现其实尽管TreeSet要进行排序,但是插入所耗费的时间,仍然没有慢多少。所以甚至有人提出利用TreeSet代替hashSet,但是这样又会涉及到一个问题,就是对对象进行排序的规则是什么?
就那正方形类举例,两个正方形对象,如何进行排序,显然是排序不了的,所以TreeSet自有他的优点,但是HashSet的优点也有很多,二者都有不可替代性。
TreeSet是如何自然排序的
TreeSet支持两种排序方式
一种是自然排序,一种是客户化排序。默认情况下,采用自然排序。
因为JDK中一部分的类实现了Comparable接口,比如Integer、String、Doublie。他们实现了Comparable接口中的compareTo方法来比较,对集合进行升序排列
而且该集合只能放入相同类型的数据
Set<String>set1=newHashSet<>();
Strings1=newString("a");
Integers2=newInteger(9);
set1.add(s1);
// set1.add(s2);
// 这里会报错,因为添加了不同类型的数据
假如该类没有实现Comparable接口,那么就会报错
Set<food> set1 = new TreeSet<>();
food f1 = new food("fish","22");
food f2 = new food("pig","15");
set1.add(f1); // 这里因为food类没有实现Comparable接口,所以会抛出ClassCastEXception
set1.add(f2);
该类实现类Comparable接口,就会按照定义的 compareTo 方法对c对象进行升序排序
我们所创建的Student类实现了Comparable接口,并且实现了 compareTo 方法,
publicintcompareTo(Objecto) {
Studentstudent=(Student)o;
returnthis.id>((Student)o).id?1:(this.id==((Student)o).id?0:-1);
}
这里的方法定义了我们是按照学号的大小排序的
Set<Student> set1 = new TreeSet<>();
Student s1 = new Student("张三",12);
Student s2 = new Student("李四",11);
set1.add(s1);
set1.add(s2);
Iterator iterator = set1.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
结果如下
为了TreeSet能够正确排序,其中的equips方法、hashCode方法和CompareTo方法比较的规则必须是相同的
除此之外,TreeSet还支持客户端排序
publicclasstestimplementsComparator{
publicstaticvoidmain(String[]args){
Set<Student>set1=newTreeSet<>(newtest());
}
@Override
publicintcompare(Objecto1,Objecto2) {
return0;
}
}
采用Comparator接口,我们创建一个类来实现Comparator接口,然后在该类中重写该类的compare方法
然后我们在创建TreeSet接口时,指定该实现类来比较对象
TreeSet集合新增的方法如下, first last comparator返回集合排序采用的比较器,headSet返回头集合,subSet,子集合 tailSet尾集合
4.Map映射
Map集合没有实现Collection接口,其提供的是Key到value 的映射,,而值对象value仍然可以是Map类型,Map中不能包含相同的key,
Map<Integer,Map<Integer,String>> map = new HashMap<>() ;
Map<Integer,String> map1 = new HashMap<>();
map1.put(11,"hsy");
map.put(213,map1);
System.out.println(map);
每个key只能对应一个value,key还决定了存储对象在映射中的存储位置,但并不是由key本身决定的,而是有一种散列技术进行处理,产生一个散列码的整数值。散列码通常用作一个偏移量,该偏移量对应分配给映射的内存区域的起始位置从而确定存储对象在映射中的存储位置,这也就是传说中的哈希码技术。
map的键对象不能重复是通过get方法来获取判断的,若以同一键值两次输入映射,后一次映射会把前一次在键值产生的映射覆盖掉
Map<String,String>map=newHashMap<>();
map.put("01","张三");
map.put("02","李四");
map.put("03","马五");
map.put("06","王二麻子");
map.put("08","侯六");
System.out.println(map.get("01"));
Set<String>set=map.keySet();
for(Stringstring:set)
System.out.println(string);
Collection<String>set1=map.values();
for(Stringstr:set1)
System.out.println(str);
Map接口,提供了key映射到值对象,一个映射不能包含重复的key,每个key最多只能映射带一个值。该接口主要提供的方法有
put(K key,Value value)添加映射关系
containsKey(Obeject key)查询是否包含key的映射关系
containsValue(Object value) 查询是否包含value的映射关系
get(Object key)获取key所连接的value值
keySet 返回该集合中所有的key对象形成的Set集合
value 返回该集合中所有值对象形成的Collection集合
接口的实现类有HashMap和TreeMap
一般使用HashMap来实现Map接口,因为HashMap类增删映射的关系更快,HashMap是通过哈希码对其内部的映射关系进行快速查找,而TreeMap中的映射关系存在一定的顺序,也就是说也存在一个遍历过程。
HashMap允许用null键和null值,但必须保证唯一性,因为要利用哈希码进行快速查找
TreeMap不仅实现了Map接口,还实现了SortedMap接口,因此其中的映射存在着一定的顺序关系,也正是因为如此,TreeMap在增减映射时与HashMap相比慢一点。TreeMap排列顺序是通过key值进行排序
Map<Integer,String>map=newTreeMap<>() ;
map.put(1,"first");
map.put(5,"five");
map.put(2,"second");
System.out.println(map);
Map的三种集合,
第一个键集合
利用KeySet方法,将会返回Map中的集合所以的所有键,返回一个Set集合,集合中的对象类型是Object
第二个键对方法
利用En'trySet方法,将会返回将Map集合中的所有对象打包返回Set集合,切集合内部对象类型是Object
第三个值集合
利用values方法,将会返回一个由value的值组成的集合Collection,其中同样的是集合内部的对象类型为Object
Map<Integer,String>hashtable=newHashtable<>(2,0.75f);
hashtable.put(11,"cc");
hashtable.put(15,"bxr");
hashtable.put(49,"hsy");
Setset=hashtable.keySet();
System.out.println("第一种集合-key");
for(Objectinteger:set)
System.out.println(integer);
Setset1=hashtable.entrySet();
System.out.println("第二种集合-key-value");
for(Objects:set1)
System.out.println(s);
Collectioncollection=hashtable.values();
System.out.println("第三章集合 - value");
for(Objectobject:collection)
System.out.println(object);
输出如下
初次之外,Map接口中,还存在着很多解决其他问题的类
1.WeakHashMap若散列映射集合
假设一个键对中的键key 不再使用了,那么会出现什么情况呢?不再有任何途径可以引用这个键的值了,同时也因为程序中的任何部分都无法出现这个键,所有或者键对在集合中再也无法删除。而且虚拟机的垃圾回收器也是无法删除他的,因为这个键对在桶中,而桶是不断变换的,这样垃圾回收器认为这个键对并非无用的,所以不会删除他的,所以就引入了若散列映射
2.LinkedHashMap和LinkedHasdSet两个类,一般这两个类的存储结构为顺序存储结构,但是在这里,我们将两个结构换成了链表式的存储结构,这样的话,查找就会慢了一些,但是插入删除就会快很多。该类在实现的时候,可以实现高速缓存,访问频率比较高的数据直接存放到内存中,只要在构造方法上将loadFactor的值设为true就可以了,但是不建议这样使用,因为这样会大量占用内存资源
3.枚举集和映射
枚举集没有公共的构造器,只能静态工厂中的方法构造实例,
EunmSet集合的四种静态工厂
publicclassMain{
// 声明枚举类,内部类
enumweekday{MONDAY,TUESDAY,WEDESDAY,THURDAY,FIRDAY,SATURDAY,SUNDAY};
publicstaticvoidmain(String[]args){
// 第一种静态工厂的静态方法,将枚举类中的所有静态实例放到枚举集合中
EnumSet<weekday>enumSet1=EnumSet.allOf(weekday.class);
// 第二种静态工厂的静态方法,不会将枚举类中的静态实例放到集合中
EnumSet<weekday>enumSet2=EnumSet.noneOf(weekday.class);
// 第三种方法,将两个静态实例中间的对象,放到枚举集合中
EnumSet<weekday>enumSet3=EnumSet.range(weekday.WEDESDAY,weekday.FIRDAY);
// 第四种,将填入静态方法中的实例放进枚举集合中
EnumSet<weekday>enumSet4=EnumSet.of(weekday.FIRDAY,weekday.MONDAY);
}
}
4.标识散列映射IdentityHashMap
在该散列中比较两个映射的键是否相等,采用的是“==“ 来比较的,而Hashcode也是采用System.identityHashCode方法计算的,也就是说,这里比较的并不是两个对象的内容,而是判断两个对象是不是同一个对象
IdentityHashMap<String,Integer>identityHashMap=newIdentityHashMap<>();
identityHashMap.put("hsy",1008);// 这样显示不可以的,因为他们两个所采用的是一个引用
identityHashMap.put("hsy",10018);// 后面的values将前面的键值给覆盖了
System.out.println("第一次的");
for(Objectobject:identityHashMap.entrySet())
System.out.println(object);// 输出 第一次的 hsy=10018
identityHashMap.clear();
Strings1=newString("hsy");
Strings2=newString("hsy");
identityHashMap.put(s1,10086);
identityHashMap.put(s2,10010);
System.out.println("第二次的");
for(Objectobj:identityHashMap.entrySet()) {
System.out.println(obj);// 输出 第二次的 hsy=10010 hsy=10086
}
5.Queue 队列
队列的特点,从头部添加元素,从尾部获取元素,先进先出从尾部添加元素,从头部删除元素
添加元素的方法,
add和offer这两个的不同之处是,如果队列已满,那么add方法会抛出IllegalStateException异常,而offer方法会返回false,大多数队列可以无线扩充,对于某些队列实现类,offer方法才会有用
删除元素的方法
remove方法和poll,不同之处,同样当队列的已经为空的时候,remove会抛出NoSuchElementException异常,poll方法会返回false方法
获取元素
element方法和peek,两个方法都会获取头部元素,但是不会删除,这两个方法同样也是存在的不同就是,当数组为空,element方法会抛出NoSuchElementException异常,而peek方法会返回false
获取、添加和删除元素都存在返回值,返回值不是基本数据类型,返回Object对象,可能是true 可能 数字
Queue<String>queue=newArrayDeque<>(2);
queue.offer("1");
queue.add("520");
// 先获取
System.out.println(queue.element());
// 获取后删除
System.out.println(queue.remove());
System.out.println(queue.peek());
System.out.println(queue.poll());
(1).Deque双向队列
Deque是Queue的子接口
因为是双向队列,所以size方法返回的并不是真正的长度
Deque接口具有向头部和尾部添加、获取和删除元素的方法
添加方法 addFirst方法、offerFirst方法 addLast方法和offerLast方法
删除元素 removeFirst方法、removeLast方法、pollFirst方法和pollLast方法
获取元素 getFirst方法、getFirst方法、peekFIrst方法、peelLast方法
同样其中方法的不同之处也是相同的,若是在这里调用Queue接口中的方法,那么也是可以的,因为该接口是Queue的子接口,而且会按照默认的规则,也就是先进先出的规则
Deque<String>queue=newArrayDeque<>(2);
queue.addFirst("1");
queue.addLast("520");
System.out.println(queue.getFirst());
System.out.println(queue.getLast());
System.out.println(queue.removeLast());
System.out.println(queue.removeFirst());
System.out.println(queue.pollLast());// 删除完了 返回NULL
System.out.println(queue.pollFirst());// 删除完了 返回NULL
(2).PriorityDeque优先级队列
优先级队列会按照排序的方式对队列中的元素进行排序和检索,因为假如到PriorityDeque中的对象必须实现Comparable接口,值得注意的是,当用foreach方法对队列进行遍历的时候,队列没有进行排序,但是通过remove方法移除元素时,每次删除最小的元素
Queue<Integer>queue=newPriorityQueue<>();
queue.add(2);
queue.add(5);
queue.add(3);
queue.add(6);
for(Integerinteger:queue) {
System.out.println(integer);
}
while(!queue.isEmpty())
System.out.println(queue.remove());
6.HashSet和HashMap的负载因子
HashMap和HashSet都运用哈希算法来存取元素,哈希表中的每个位置也称为桶bucket,在发生哈希冲突的时候,在桶中以链表的方式存放多个元素
容量:哈希表中桶 capacity 的个数。
初始容量:创建HashMap和HashSet对象时的桶的数量,在这两个对象的构造方法运行设置输出容量。
大小,元素的数目。
负载因子: 等于size/capacity。负载因子为零,则表示空的哈希表。负载因子为0.5,则表示半满的哈希表,由此来判断哈希冲突的程度。轻负载的哈希表具有冲突少、适合插入和查找的优点,但是Iterator遍历元素的速度较慢,HashMap和HashSet的构造方法允许指定负载因子,当哈希表中的当前负载达到用户所设定的负载因子时,HashSet和HashMap会成倍的增加容量,并且重新分配元素的位置
负载因子的设置,可以让我们的操作在时间复杂度和空间复杂度中有一个很好的权衡
7.Collections 集合实用类
与Arrays控制Array数组一样,Collections类也是专用控制集合类的,该实用类有一部分静态方法专门操纵List集合类,有一部分静态方法专门操作Map集合、Collection类型
该类中有很多可以对集合操作的方法
适用于List集合的方法
copy(List begin , List end) 复制数组中的元素
fill (List)
sort 对集合中的元素进行自然排列
binarySearch(List list,Object obj) 对集合中的元素按照自然排序情况下的二分查找
如果元素在集合中,就会直接返回元素的索引,但是假如集合中没有该元素,那么返回负值,我们可以根据这个复制找到新元素合适的插入位置来保证集合依然时有序的
List<Integer>list=newArrayList<>();
for(inti=0;i<100;i++)
if(i%2==0)
list.add(i);
intx=Collections.binarySearch(list,10);
System.out.println(x);
inty=Collections.binarySearch(list,27);
System.out.println(y);// 我们就可以把值插入插入位置为 -y-1
list.add(-y-1,27);
for(Integeri:list) {
System.out.print(i+" ");
}
binarySearch(List list,Object obj, Comparator c) 按照c中compareTo方法比较方法进行二分查找
完成之后进行输出,我们发现集合仍然是有序的
shuffle随机排列
max 同样两种方法找最大值
min 同上
singleton返回一个不可改变的Set集合,它只包含一个参数指定的对象
singletionMap同样如此
synchronizedCollection返回线程安全的集合
synchronizedList
synchronizedMap
synchronizedSet
unmodifiableCollection 返回一个不允许修改的Collection集合视图
unmodifiableList
unmodifiableMap
unmodifiableSet
正常数组中能进行的操作,在集合中都是存在相应算法的,有很多在数组中没有的操作,但是在集合中仍然是相应的算法的
8.线程安全的集合
在Java集合中以上四种的集合接口的实现类List 、 Set 、Map、Queue都没有采取同步机制,也就是说都不是线程安全的,在单线程的情况下,虚拟机不用因为管理同步锁而产生额外的开销,但是在多线程的情况下,这样是不行的。为了避免并发问题,基本要采取以下几种措施
(1)在程序中对可能导致并发问题的代码模块进行同步
(2)第二种方法就是采用集合的同步锁,获得原始集合的同步版本
Collection<String>collection=newArrayList<>();
Collection<String>collections=Collections.synchronizedCollection(collection);
集合转为数组,采用Collection接口中的toArray方法
List<String>list=newArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Object[]obj=list.toArray();
每个集合都有这种类似的方法
数组转为集合,采用Arrays类的asArray方法
Integer[]integers={1,2,3,4,5,6,7,8,9,0};
List<Integer>list=Arrays.asList(integers);
9.集合的批量操作
在Collection接口下的有很多对数组进行批量操作的方法
boolean reatainAll (Collection<?> c) 修改当前集合,在当前集合中保留那些同时为c中的元素,删除其余的元素,如果最终做了修改的操作,返回true,反之,则返回false
boolean removeAll(Collection<?> c) 删除同时在c中的元素
boolean addAll(Collection<?> c) 添加c中的元素
boolean containsAll(Collection<?> c)判断是否集合中是否含有c集合中的元素
10.历史集合类
在JDK1.0时期,Java的集合类Vector、Stack、Enumeration、Hashtable、Properties和BitSet类,JDK1.2才出现了Collection、Set、List、Map接口,在JDK1.5才出现了Queue接口,
Vector类同样实现了List集合接口,因为Vector采用了同步化机制,也就是说,Vector是线程安全的,但是操作速度比较慢,而List接口的其他实现类都是线程不安全的,所有操作速度比较快
这样来看List接口就存在三个实现类,分别是Vector类、ArrayList类、LinkedList类
而linkedList类又实现类ListIterator接口,而Vector类又有子类Stack类(堆栈),但是因为该类继承了Vector类,所以不算真正意义上的堆栈,因为可以在集合中间对集合进行查询
Stack堆栈,因为该类实现了Vector接口,并不是严格堆栈,采取了同步化机制,线程安全的集合现在对应着Linkedlist
堆栈的方法有三个,
第一个push(item),将item对象压入栈中,并且返回item
第二个pop(),弹出并返回栈顶的对象,如果栈是空的,会抛出异常
第三个 peek() ,返回栈顶元素,并不弹出,如果栈为空,抛出异常
Hashtable是Map接口的另一个实现类,也就是Map接口现在有三个实现类,分别是HashMap类、TreeMap类和Hashtable类,同样地,Hashtable是线程安全的
而TreeMap类和HashMap类是线程不安全的
Properties类是Hashtable类的子类,同样是线程安全的,该类的属性和方法与HashMap基本一致,并且有新的方法,该类的load方法可以直接从数据流中读取文件
Enumeration 对应着Iterator接口,为这些历史集合类,提供遍历条件,主要有Hashtable类,Properties类、Vector类提供方法
BitSet存放boolean类型的数据
BitSet最小集合容量为64位,也可以通过构造方法来设置容量
其中含有的方法有
Set(int index)将index位置的数值由false变成true
clear(int index)清楚index位置的元素
get
and(BitSet bs)进行与运算,结果保存在BitSet中
or(BitSet bs)
xor(BitSet bs)
12枚举类型
JDK5之后,提供了抽象的Enum枚举类,同时还创建了Enum枚举类,该类实现了枚举接口,
我们可以自定义类继承Enum类,然后在自定义的类中创建静态常量
枚举类型相当于一静态内部类
同样也是可以设置构造方法
JDK5以后,再一次简化了枚举类型的使用
classGender{
enumSIZE{BIG,SMALL}
}
这里我们采用enum关键字创建枚举类型,相当于创建了Enum的子类,并且在里面放置静态常量
Enum接口还给出了一些方法
compareTo方法
getDeclaringClass() 返回当前枚举常量的枚举类型的Class对象
name返回枚举类型的名称
ordinal返回枚举常量的序数