Java集合框架
集合的概念
集合,也称为容器,是一种工具类,它可以将一系列具有共同特性的元素组合成一个单元,用于存储,提取,管理.JDK提供的集合API都包含在java.util包内.
Java集合框架主要分为两大部分,一部分实现了collection接口,该接口定义了存取一组对象的方法,其主要子接口为:Set和List,另外一部分是Mapjko,该接口定义了存储一组”键(key)值(value)”映射对的方法.
集合框架体系结构
开发过程中由于需求的不同,Java集合提供了不同的集合类类,Java集合包括8个主要的实现类,对接口的具体实现,我们主要学习常用的接口和实现类:
学习过程我们主要学习三大类接口:
Collection接口
Collection接口中包含了集合的基本操作和属性.Collection包含了List和Set两大分支.
- List是一个有序的队列,可以存放重复的元素,每一个元素都有对应的索引,第一个元素的索引值是0,List接口的实现类有:ArrayList,Vector和Stack.
- 2. Set是一个不允许有重复元素的集合.Set接口的实现类有HashSet和TreeSet.
使用Collection接口需要注意:
- Collection接口是List, Set, Queen接口的父接口
- Collection接口中定义了可用于操作List, Set, Queen接口的方法,也就是CRUD
- Set接口中的数据对象没有顺序且不可以重复,List接口中的数据对象有顺序且可以重复.
Collection接口是集合的顶层接口,定义了集合的共性功能:
成员方法:
- A. 添加功能
add(Object obj):向集合中添加元素,不允许重复
add(Collection<T> obj):将指定集合的元素添加到该集合
- B. 删除功能
remove(Object obj):从集合中删除一个元素
removeAll(Collection obj):从该集合中删除同时包含在指定集合中的元素
- C. 判断功能
contains(Object obj):判断集合中是否包含指定的元素
containsAll(Collection obj):判断集合中是否包含另一个集合的元素
isEmpty():判断集合是否为空
- D. 交集功能
retainAll(Collection obj):仅保留该集合中同时包含在指定集合中的元素
- E. 迭代器
Iterator iterator():返回一个Iterator对象,用来遍历集合中的数据元素
- F. 长度功能
Int size():返回集合中元素的个数
- G. 集合转换数组
toArray():返回一个包含集合中所有数据元素的数组
toArray(T[] t):用来获取一个包含所有元素的指定类型的数组
public static void main(String[] args) {
Collection<Integer> colls = new ArrayList<>();
for (int k = 0; k < 10; k++) {
colls.add((int)(Math.random()*10));
}
Iterator it = colls.iterator();
System.out.print("集合遍历:");
while(it.hasNext()){
System.out.print(it.next()+" ");
}
System.out.println("=========");
//System.out.println(colls.remove(6));
System.out.println("集合大小:"+colls.size());
Collection<Integer> colls2 = new ArrayList<>();
colls2.add(8);
colls2.add(5);
colls.removeAll(colls2);
}
练习:
创建狗对象(带参数)[name, age, type],存储到集合,用迭代器进行遍历并打印对象的属性数据值
Set集合
Set接口主要实现:HashSet和TreeSet.
HashSet类
HashSet类用于存储不重复的元素,该类由哈希表支持(实际上是一个HashMap集合).在使用HashSet集合时,HashSet集合不能保证迭代顺序与元素存储顺序相同.其因为是采用哈希表结构存储数据,其保证数据唯一性的方式依赖于:hashCode()与equals()方法.
哈希表
哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数据结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。
总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
面试题:
如果两个对象的equals方法返回true,p1.equals(p2)==true两个对象的哈希值一定相同吗??? (一定)
hahSet存储元素
给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。
//创建HashSet对象
HashSet<String> hs = new HashSet<String>();
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
练习:
获取10个1到20 之间的随机数,要求随机数不能重复,并且把最终的随机数输出???
LinkedHashSet介绍
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。
TreeSet类
TreeSet是sortedSet接口的实现类,TreeSet可以确保集合元素处于有序的排列状态,TreeSet实现排序分为两种类型:自然排序和定制排序.默认情况下TreeSet采用自然排序.
自然排序
TreeSet会调用集合CompareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,但试图把对象类型添加到TreeSet集合时,则该对象的类必须要实现Comparable接口重写compareTo()方法[基本数据类型以及String类都有实现Comparable接口并且都重写了compareTo()方法];
对象类型实现Comparable接口的compareTo()方法其比较规则:
两个对象即通过CompareTo(Object obj)方法返回值来比较大小,如果返回值是0,说明元素重复,只存储第一个,如果返回值大于0,升序存储,如果返回值小于0,倒序存储.
Comparable的典型实现:
- BigInteger[大整数类]和BigDecimal[大浮点数类]以及所有的数值类型对应的包装类:按照它们的数值大小比较.
- Character:按照字符的unicode值进行比较
- Boolean:true对应的包装类实例大于false对应的包装实例
- String:按照字符串中字符的unicode的值进行比较
- Date,Time后边的时间,日期比前面的时间,日期大.
向TreeSet中添加元素时,只有第一个元素无须比较CompareTo()方法,后面添加的所有元素都会调用CompareTo()方法进行比较.
TreeSet在实现排序时,只有相同类型的实例才会比较大小,所以向TreeSet中添加的应该是同一个类型的数据.对于TreeSet集合而言,判断两个对象是否相等的唯一标准是:两个对象通过CompareTo(Object obj)方法比较返回值,当需要把一个对象放入TreeSet中,重写该对象对应的equals()方法时,应保证该方法与CompareTo(Object obj)方法有一致的结果,如果两个对象通过equals()方法返回true,则通过CompareTo(Object obj)方法比较应该返回0.
Eg:
/*
* 重写CompareTo()实现倒序排序????
分析: 要实现倒序就要重写CompareTo()方法,改变其排序规则
*/
public static void main(String[] args) {
TreeSet<Integer> intNum = new TreeSet<Integer>(
new Comparator<Integer>() {
@Override // 匿名内部类
public int compare(Integer o1, Integer o2) {
// return o1-o2; //升序
return o2 - o1;// 倒序
}
});
intNum.add(90);
intNum.add(60);
intNum.add(80);
intNum.add(87);
intNum.add(67);
intNum.add(100);
for (Integer intSet : intNum) {
System.out.print(intSet + " ");
}
}
定制排序
TreeSet的自然排序是根据集合元素的大小,进行升序排列,如果需要自定义排序规则,就需要通过Comparator接口来实现,重写compare(T o1, T o2)方法.利用此方法比较o1和o2的大小,如果返回整数o1>o2,如果返回0 o1=o2,如果返回负数o1<o2
当把对象类型添加到TreeSet集合时,必要要实现Comparabale接口重写CompareTo()方法,或则会出错:
Eg:
//比较器代码:比较年龄,如果年龄相同比较姓名
@Override[内部比较器]
public int compareTo(Object o) {
System.out.println("this.age"+this.age); //???20 20 22
Student s = (Student)o;
System.out.println("s.age"+s.age); //???20 22 21
//对年龄进行排序,如果年龄相同就比较名字???
//int res = this.age-s.age;
int res =this.age<s.age?1:(this.age==s.age?0:-1);
//if(res==0){
//res = this.name.compareTo(s.name);
//}
int num2 = res==0?this.name.compareTo(s.name):res;
return num2; //升序
//return s.age-this.age; //倒序
}
TreeSet<Student> stuTree = new TreeSet<>(
//使用匿名内部类来实现[外部比较器]
new Comparator<Student>() {
//比较器代码:比较年龄,如果年龄相同比较姓名
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge()-s2.getAge();
int num2=num==0?s1.getName().compareTo(s2.getName()):num;
return num2;
}
}
);
练习:
以方法的形式接收参数,方法实现对其中所有的字符进行排序,
Eg:hellowllanqiao 程序打印排序后的字符串:aaehillllnooqw
public class TreeSetdemo2 {
public static void main(String[] args) {
String str = "hellowllanqiao";
treeSetChar(str);
}
public static void treeSetChar(String str) {
// 通过toCharArray()将字符串转换为字符数组
char[] charArr = str.toCharArray();
// 通过外部比较器实现保留重复的字符
TreeSet<Character> ts = new TreeSet<>(
new Comparator<Character>() {
@Override
public int compare(Character c1, Character c2) {
//int num = c1.compareTo(c2);
int num = c1-c2; //自动拆箱
//return num;
return num == 0 ? 1 : num;
}
});
for (char c : charArr) {
// 将遍历的字符存储到TreeSet集合中[可以实现排序]
ts.add(c);
}
// 遍历TreeSet()集合实现打印输出
for (Character charSet : ts) {
System.out.print(charSet + " ");
}
}
}
小结
面试题:
Hashset与TreeSet的异同???
相同点:
都是set接口的实现类,对应的元素不可重复.
不同点:
- 底层存储的数据结构不同
HashSet底层用的是hashMap哈希表结构存储,而TreeSet底层用的是二叉树结构存储.
- 存储时保证数据唯一性的依据不同
HashSet是通过重写hashCode()方法和equals()方法来保证数据的唯一性,而TreeSet是通过Compareable接口的compareTo()方法来保证数据的唯一性.
- HashSet存储数据是无序的,Tree有序
List集合
List接口是collsction接口的子接口.List集合类中的元素都是有序的并且可以重复.集合中的每个元素都有其对应的顺序索引.
List集合中的元素都对应一个整数形的序号记载其在容器中的位置,可以根据序号获取容器中的元素.
List接口常用的实现类有:ArrayList, linkedList, Vector
List接口成员方法
- 添加的方法
Void add(Object obj):在指定位置添加元素
- 移除的方法
Object remove(int index):通过指定的索引删除元素,并把删除的元素返回
- 获取的方法
Object get(int index):返回列表中指定位置的元素
- 替换的方法
Object set(int index, Object obj):替换指定位置的元素
- List的排序
升序排序:Collections.sort(list)
随机排序:Collections.shuffle(list)
倒序排序:Collections.reverse(list)
- 查找指定位置的元素
Int indexOf(Object obj):返回集合中第一次出现的指定元素的索引
Int lastindexOf(Object obj): 返回集合中最后一次出现的指定元素的索引
- List集合的遍历
For(普通for循环)
Iterator(迭代器)
Foreach(增强for循环)
ArrayList类
ArrayList实现了List接口,是List集合中使用最频繁的实现类,其线程不安全的.在存储方式上ArrayList采用的是数组数进行顺序存储,底层对数组进行了封装,实现了可变长度的数组,在插入或删除元素时,性能较差,查询元素或遍历元素时效率较高,基本用法同list接口中的成员方法.
LinkedList类
LinkedList和ArrayList在逻辑结构上没有什么区别,只是底层数据存储结构上存在差异,LinkedList采用的是链表进行链式存储.
LinkeList成员方法
- Void addFirst(Object obj): 将指定数据元素插入到集合的开头
- Void addLast(Object obj): 将指定数据元素插入到集合的末尾
- Object getFirst(): 返回集合的第一个元素
- Object getLast(): 返回集合的最后一个元素
- Object removeFirst(): 移除并返回此集合的第一个数据元素
- Object removeLast():移除并返回此集合的最后一个数据元素
Eg:
从1-28之间随机抽取9个不重复的数字
List<Integer> listNum = new ArrayList<>();
for (int k = 1; k <=28; k++) {
listNum.add(k);
}
//用于生产随机数
Random random = new Random();
for (int x = 0; x < 9; x++) {
//random.nextInt(28)===>[0,27]
int num = random.nextInt(listNum.size());
System.out.print(listNum.get(num)+" ");
listNum.remove(num);
}
产生验证码 0--9,a--z,A--Z随机取四个数作为索引值产生验证码
List listSode = new ArrayList<>();
//生成0-9
for (int y = 0; y <=9; y++) {
listSode.add(y);
}
//生成a-z[97-122]
for (int k = 97; k <=122; k++) {
listSode.add((char)k);
}
//生成A-Z[65-90]
for (int z = 65; z <=90; z++) {
listSode.add((char)z);
}
for (Object object : listSode) {
System.out.print(object);
}
System.out.println("========="+listSode.size());
Random ranCode = new Random();
for (int n = 0; n <4; n++) {//[0-61]
System.out.print(listSode.get(ranCode.nextInt(listSode.size())));
}
面试题
ArrayList与LinkedList的异同???
相同点:
存储元素都是有序的,元素可以重复,线程不安全
不同点:
Arraylist:底层数据结构是数组,查询快,增删慢
LinkedList:底层数据结构是链表,查询慢,增删快
使用场景:
当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会效率比较高,
当操作的是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应当使用LinkedList.
Vector类
Vector是一个比较古老的集合类,JDK1.0就有了,大多数操作和ArrayList相同,区别之处就是Vector是线程安全的.Vector实现类早使用过程中的效率总是比ArrayList慢,所以在开发过程中尽量避免使用.
Collections工具类
Collections是一个操作Set, List, Map等集合的工具类.Collections中提供了一系列静态方法对集合元素进行排序,查询,修改的操作
排序方法
排序方法均为static方法
- Reverse(list):反转list中元素的顺序
- Shuffle(list):随机排序
- Sort(list):升序排序
- Sort(list, Comparator):根据指定的Comparator产生的顺序对list集合元素进行排序
- Swap(list, int i, int j ):将指定list集合中的i处元素和j处元素进行交换
- Object max(Collection):根据元素的自然排序,返回给定集合中的最大元素
- Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection, Comparator)
- Int frequency(Collection, Object):返回指定集合中指定元素出现的次数
- Void copy(List dest, List src):将src集合中的元素复制到dest集合中
- Void fill(List list, Object o):使用指定数据元素替换指定集合中的所有数据元素
- Boolean replaceAll(List list, Object old, Object new):用新值替换List对象的所有旧值
查找与替换
练习:
键盘输入接收多个整数,直到输入Y(y)时结束输入,把所有输入的整数排列[倒序]打印
public static void main(String[] args) {
ArrayList<Integer> listNum = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
System.err.println("请输入整数,当输入Y(y)结束");
while (true) {
//next()
//nextLine()
String strNum = scanner.nextLine();
//equalsIgnoreCase()不区别大小写的比较
if(strNum.equalsIgnoreCase("Y")){
break; //跳出[终止]循环
//continue; //跳出本次循环,继续下一次的循环
}
listNum.add(Integer.parseInt(strNum));
}
System.out.println("排序之前的输出:");
for (Integer intNum: listNum) {
System.out.print(intNum+" ");
}
System.out.println();
//对集合排序[升序]
Collections.sort(listNum);
//对集合反转[倒序]
Collections.reverse(listNum);
//Collections.sort(list, c);???
System.out.println("排序之后的输出:");
for (Integer intSortNum : listNum) {
System.out.print(intSortNum+" ");
}
}
集合比较器
Comparable内部比较器
之前使用Comparable接口实现了TreeSet集合中的自定义排序,这种方式是通过在集合内的元素类中实现CompareTo(Object obj)方法进行实现的.因为是在类的内部实现的其成为内部比较器.
对于没有实现Comparable接口的CompareTo()方法的集合要实现排序需要调用Collections工具类的sort(List list)方法来实现.
Comparator外部比较器
Comparable称为内部比较器那么自然就有外部比较器,也就是在学习Collections工具类的sort(List list, Comparator)方法时提到的比较器Comparator.
Comparator称为外部比较器,comparator接口中有一个用于比较的方法compare(T o1, T o2),其排序实现方式是:o1<o2返回负数,o1= o2表示相等,o1> o2返回正数
练习:
要求:公司要将员工进行排序,假设先进行姓排序,谁的拼音靠前谁就排在前面.然后对名字进行排序,同姓,谁的名拼音靠前谁就排在前面,Eg:如果同姓又同名,则女性排前头,如果名字和性别都相同,年龄小的排前头.[实体类:firstname,lastname,sex,age]
//所谓的工具类的特征:既不存在继承也不存在实现,它是一个独立的个类,
//只是用于帮助实现某一个具体的功能
//工具类中的方法一般都是静态方法
public class ComparatorUtils {
/*
* 要求:公司要将员工进行排序,假设先进行姓排序,
* 谁的拼音靠前谁就排在前面.然后对名字进行排序,
* 同姓,谁的名拼音靠前谁就排在前面,
* Eg:同姓同名,则女性排前头,如果名字和性别都相同,年龄小的排前头.
* [实体类:firstname,lastname,sex,age]
*/
public static Comparator getComparator(){
//匿名内部类
return new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof PersonStu){
return compareSet((PersonStu)o1, (PersonStu)o2);
}else{
System.err.println("没有找到合适的比较器");
return 0;
}
}
};
}
//相当于重写compare()方法
public static int compareSet(PersonStu obj1, PersonStu obj2){
//比较姓
int res = obj1.getFirstname().compareTo(obj2.getFirstname());
//同姓判断名
if(res==0){
res = obj1.getLastname().compareTo(obj2.getLastname());
}
//同名同姓,判断性别
//res =obj1.getSex().compareTo(obj2.getSex());
if(res==0){
res = obj1.equals(obj2)?0:(obj1.getSex().booleanValue()==true?1:-1);
}
//同名同姓同性别,判断年龄
if(res==0){
res=obj1.getAge()-obj2.getAge();
}
return res;
}
概念
线程
线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护措施,其他线程就不能进行访问直到该线程读取完.其他线程才可使用.不会出现数据的不一致或者数据不准确问题.
线程不安全:就是不提供数据访问的保护措施,有可能出现多个线程先后更改数据造成所得到的数据不准确.
同步
多个线程操作一个资源的情况下,导致资源数据前后不一致.这样需要协调线程的调度,即线程同步.
Eg:
假设在火车上,只有一个厕所,ABC三个人都在排队
简单来说:
线程同步:
就是说A进去,门上锁了,BC只能在外面干等着,着就是线程安全,不会产生问题
线程不同步:
就是说A进去的同时,门没有关,B也可以进行,这样就是线程不安全.
Iterator接口
Iterator是遍历集合的工具,通常使用Iterator迭代器来遍历集合.Collection的实现类都要实现iterator()方法,返回一个Iterator对象.ListIterator是专门为遍历List而存在的.Iterator接口提供了三个常用的方法:
- Object next():返回迭代的下一个元素
- boolean hasNext():判断是否存在下一个可访问的数据元素
- void remove():从迭代器指向的集合中移除迭代器返回的最后一个数据元素
迭代器遍历集合:
迭代器是依赖于集合而存在的,要想得到迭代器对象必须要先有集合对象.其迭代步骤:
Foreach遍历
Foreach也叫增强for循环
语法:
说明:
- foreach简化了数组和集合的遍历,如果不需要遍历整个集合或者在循环内部需要操作索引[小标]就需要使用传统for循环.
- foreach简化了编程代码,提高了代码的可读性和安全性(防止数组越界)
- foreach一般集合泛型使用
Map接口
Map接口定义了存储一一对应的”键(key)值(value)”对的映射方法.Mapjko是将对应的键映射到值,Map集合中不能包含重复的键,每个键最多只能映射到一值.
Map接口中常用的实现类有:HashMap, TreeMap, Hashtable, LinkedHashMap.
在Map中:
- HashMap主要用于插入,删除和定位元素[键是通过哈希算法实现]
- TreeMap主要用于定义自然排序或自定义排序[键是二叉树算法实现]
- LinkedHashMap主要用于输出的,输入和输出的次序相同.
Map接口的特点
- Map接口提供的是键值映射,其中的元素是以键值对的形式存储,能够实现根据Key快速找到value
- Map中的键值对是以Entry类型的对象实例形式存在,键(key)是不可以重复,value值可以
- 每一个键最多只能映射一个值
- Map接口提供了分别返回Key值集合和Value值集合以及Entry(键值对)集合的方法.
- Map集合中的值是通过key来标识,即同一个Map对象的任何两个值通过equals()方法比较总是返回false.
Map接口中的方法
- A. 删除功能
Void clear():移除集合中的所有键值对元素
V Remove(Object key):根据键移除值对元素,并返回
- B. 判断功能
ContainsKey(Object key):判断集合中是否包含指定的键
ContainsValue(Object value):判断集合中是否包含指定的值
- C. 获取功能
Set<Map.Entry<K,V>> entrySet()
获取键值对对象的集合,遍历键值对对象,利用getkey(),getValue()获取键和值
V get(Object key):根据键获取值
Set(k) keySet():获取所有的键
Collection<v> values():获取所有的
- D. 添加功能
Object put(Object key, Object value):集合中添加元素
- E. 长度功能
Int size():获取集合中键值对的个数
HashMap
HashMap是Map接口使用最频繁的一个实现类,不允许重复,与HashSet一样不能保证映射顺序,底层使用哈希算法实现,遍历时取得数据的顺序完全是随机的.HashMap允许一条记录的键为null,允许多条记录的值为null.
HashMap不支持线程同步[线程不安全],即任意时刻都可以有多条线程同时写HashMap,这样会导致数据的不一致,如果在开发过程中使用HashMap需要同步,可以使用Collections工具类的Collections.synchronizedMap()使hashMap具有同步的功能.HashMap实现类重写了toString(),方法总是返回如下的格式的字符串:{key1=value1, Key2=value2}
HashMap特点
- 1. 基于哈希表实现,线程不安全
- 2. hashMap中的Entry对象是无序排列的
- 3. key值和value值都可以为null,但是hashMap中只能允许一个key值为null[key值不允许重复]
- 通过键找值
Map集合遍历
a) 获取所有键的集合
b) 遍历键的集合,获取到每一个键
c) 通过键映射到对应的值
- 通过键值对,找键,找值的方式来实现
public static void main(String[] args) {
//线程不同步[同步]
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
//给集合添加数据
map.put("A", "a");
map.put("E", "e");
map.put("W", "w");
map.put("H", "h");
map.put("Q", "q");
//当后面存入的元素和前面的键的值相同的时候,前面元素的值会被后面的元素的值覆盖
map.put("A", "d");
//遍历Map集合
System.out.println("第一种:通过键找值,通过Map.keySet遍历key和value");
System.out.println(map.toString());
Set<String> keySet = map.keySet();
for (String str : keySet) {
//System.out.print(str+" "); //输出Map集合中所有的key值
//输出Map集合中的所有的value值
System.out.print(str+" "+map.get(str)+" ");
}
System.out.println("第二种:通过键值对,找键找值,通过Map.entrySet使用迭代器遍历key和value");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String, String> entry = it.next();
System.out.println(entry.getKey()); //获取对应的键
System.out.println(entry.getValue());//获取对应的值
}
System.out.println("第三种:通过Map.entrySet遍历key和value");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey().toString()+" "+entry.getValue());
}
System.out.println("第四种:通过Map.values()遍历所有的value,但不能遍历key");
for (String strValue : map.values()) {
System.out.println(strValue);
}
}
练习:
统计字符串中每个字符出现的次数 Eg:EEEEEEFFFFFKKKKAAAAEEECCCDDDAA
public static void countChar(String str){
//将字符串转换为字符数组
char[] arrStr = str.toCharArray();
HashMap<Character, Integer> hm = new HashMap<>();
//遍历char数组获取每一个字符,并将字符添加到map集合中
for (char c : arrStr) {
// if(!hm.containsKey(c)){
// hm.put(c, hm.get(c)+1);
// }else{
// hm.put(c, hm.get(c)+1);
// }
hm.put(c, !hm.containsKey(c)?1:hm.get(c)+1);
}
//遍历map集合
for (char key : hm.keySet()) {
System.out.print(key+"="+hm.get(key)+" ");
}
}
如果是使用Map集合存储对象类型必须重写equals()和hashCode()方法
Set<Student> keySet = map.keySet();
此时的set实质上是Hashset,元素唯一,无序
TreeMap
Map接口派生了一个SortedMap子接口,TreeMap为其实现类,类似TreeSet排序,TreeMap也是基于树结构对TreeMap中所有的Key进行排序,从而保证TreeMap中所有的Key-value对处于有序状态,TreeMap两种排序:
- 自然排序
TreeMap的所有key必须实现Comparable接口,而且所有key应该属于同一个类的对象,否则将会抛出ClassCastException异常.
- 定制排序
创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有key进行排序.
根据value排序
对TreeMap中的value进行排序,需要借助于Collections的sort(List<T> list, Comparator<? Super T>c)方法,该方法根据指定比较器生产的顺序对指定列表进行排序.但是有一个前提条件,那就是所有的元素都必须能够根据所提供的比较器来进行比较.这种方式通用于key和value排序.
public static void main(String[] args) {
Map<String, Integer> map = new TreeMap<>();
map.put("A", 30);
map.put("E", 50);
map.put("W", 20);
map.put("K", 90);
map.put("X", 70);
map.put("C", 10);
//需求:对value排序
//将Set[map.keySet()]集合转换为list
List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(map.entrySet());
//通过比较器来实现排序
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> e1, Entry<String, Integer> e2) {
//return e1.getKey().compareTo(e2.getKey()); //对key进行排序
//对value进行排序
return e1.getValue().compareTo(e2.getValue()) ; }
});
for (Map.Entry<String, Integer> str : list) {
System.out.println(str.getKey()+" "+str.getValue() );
}
}
Hashtable
Key和value都不允许为null,线程同步,即任一时刻只有一个线程能操作hashtable,相应的效率较低.
面试题:
HashMap与Hashtable的区别???
共同点:底层都是哈希算法实现的,其都是实现Map接口
区别:
- 1. HashMap是线程不安全,Hashtable是线程安全
- 2. hshMap的key可以为空,仅有一个key可以为null,Hashtable的key不允许为空
LinkeHashMap
LinkedHashMap主要用于保存插入元素的顺序,线程非同步[不安全],在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历时会比HashMap要慢.key和value均允许为空.
对于非同步的集合,可以使用锁的机制保障某一段代码上只有一条线程访问共用资源,使其他的线程不能访问.可以使用collections的syschronizedMap方法使非同步的接口实现类[HashMap]具有同步的能力
Collections.synchronizedList(list)
Collections.synchronizedMap(map)
或者要么直接使用
//线程同步的类
ConcurrentHashMap hashMaps = new ConcurrentHashMap();
面试题:
HashMap与TreeMap的异同点
相同点:
主要用于存储键(key)值(value)对,根据键得到值,因此键不允许重复,但值可以
不同点:
- 1. HashMap里面存入的键值对在取出时是随机,也是开发过程中最常用的实现类,根据键就可以直接获取对应的值,在map中插入,删除和定位元素,hashMap是最好的选择.
- 2. TreeMap取出来的是已经排序好的键值对,但如果要按自然排序或者定制排序,TreeMap会更好.
综合练习:
模拟斗地主洗牌和发牌并对牌进行排序,
具体规则:
- 1. 组装54张扑克牌
- 2. 将54张牌顺序随机处理
- 3. 三个玩家[一个地主,两个玩家],三个交替摸牌,每人17张牌,最后留三张底牌.
- 4. 查看三个人各自手中的牌(按照牌的大小排序):大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3
public static void main(String[] args) {
/*
* 具体规则:
1. 组装54张扑克牌
2. 将54张牌顺序随机处理
3. 三个玩家[一个地主,两个玩家],三个交替摸牌,
每人17张牌,最后留三张底牌.
4. 查看三个人各自手中的牌(按照牌的大小排序):
大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3
*/
//组合牌
//创建Map集合组合牌,key编号,value是牌[花色+点数]
HashMap<Integer, String> mapPooker = new HashMap<>();
//创建List集合存储编号
ArrayList<Integer> listPookerNum = new ArrayList<>();
//创建花色数组
String[] colors = {"?","?","?","?"};
//创建点数数组[52+2个鬼牌]
String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
//拼接扑克牌[花色+点数],将花色和点数值存储到Map集合
int index =0;
mapPooker.put(index, "大王");
listPookerNum.add(index);
index++;
mapPooker.put(index, "小王");
listPookerNum.add(index);
index++;
for (String strNum : numbers) {
for (String strColor : colors) {
mapPooker.put(index, strColor.concat(strNum));
listPookerNum.add(index);//作用???[主要用于对key进行随机排序]===>洗牌
index++;
}
}
//随机洗牌
Collections.shuffle(listPookerNum);
//发牌并且排序
TreeSet<Integer> play1 = new TreeSet<>();
TreeSet<Integer> play2 = new TreeSet<>();
TreeSet<Integer> play3 = new TreeSet<>();
//存放底牌
TreeSet<Integer> bottomNum = new TreeSet<>();
for (int k = 0; k <listPookerNum.size() ; k++) {
//先拿出3张底牌[前三张]
if(k<3){
bottomNum.add(listPookerNum.get(k));
}else if(k%3==0){
//发给玩家1的牌
play1.add(listPookerNum.get(k));
}else if(k%3==1){
//发给玩家1的牌
play2.add(listPookerNum.get(k));
}else if(k%3==2){
//发给玩家1的牌
play3.add(listPookerNum.get(k));
}
}
//看牌
/*
* 分析:
* 需要指定地主 [name]
* 指定那一份牌发给地主 [牌]
* 地主看牌 [牌值]
*/
lookPookers("农民1", play1, mapPooker);
lookPookers("农民2", play2, mapPooker);
lookPookers("地主", play3, mapPooker);
lookPookers("底牌", bottomNum, mapPooker);
}
private static void lookPookers(String name, TreeSet<Integer> play, HashMap<Integer, String> pooker){
System.out.print(name+":");
for (Integer key : play) {
String value = pooker.get(key);
System.out.print(value+" ");
}
System.out.println();
System.out.println();
}
总结
- A. List, Set, Map可以看做集合的三大类
- List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问
- Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问
- Map集合中保存key-value键值对形式的元素,访问时只能根据每项元素的key来访问其value.
- B. Set和list对比
Set:检索元素效率低,删除和插入效率高,插入和删除不会引起元素位置的改变
List:和数组类似,list可以动态增长查询元素效率高,插入和删除元素效率低,因为会引起其他元素位置改变.
Set和list具体子类:
- Collection集合
- List[存取有序,有索引,可以重复]
a) ArrayList
底层是数组实现的,线程不安全,查找和修改快,增加和删除比较慢
b) LinkedList
底层是链表实现,线程不安全,增加和删除比较快,查找和修改比较慢
c) Vector
底层是数组实现的,线程安全的无论增删改查都比较慢
小结:
查找和修改比较多就用ArrayList
增加和删除比较多就用LinkedList
如果CRUD都比较多就用ArrayList
- Set[存取无序,无索引,不可以重复]
a) HashSet
底层是哈希算法实现
LinkedHashSet是hashSet的子类
底层是链表实现,可以保证元素的唯一和HashSet实现原理一样
b) TreeSet
底层是二叉树算法实现
小结:
一般在开发过程中如果不需要对存储的元素排序,大多数情况下使用hashSet.
- Map
a) HashMap
底层是哈希算法,主要针对键(key)
LinkedHashMap
底层是链表,针对键(key)
b) TreeMap
底层是二叉树算法,针对键,有序
小结:
开发过程中主要使用hashMap
- D. 集合遍历
- Iterator接口
- 2. Foreach循环
- E. 线程同步问题
Java集合框架的三大实现类:hashSet, ArrayList, HashMap其都是线程不安全的,即非同步的类,在开发过程中建议使用Collections.synchronizedCollection(c)方法来处理原本非同步的实现类.java1.5之后提供Collections.synchronizedMap(m)适用于高并发的线程安全实现.
泛型
泛型是一种把明确数据类型的工作推迟到创建对象或者调用方法时才去明确类型的特殊类型
泛型是JDK1.5新加入的特性,其主要用于解决数据类型的安全问题,主要原理是在类声明时通过一个标识表示类中某个属性的类型或某个方法的返回值及参数类型,这样在类声明或实现时只要指定需要具体的类型即可.
- 泛型的特征
a) 把运行时期问题提前到了编译时期
b) 避免强制类型转换
c) 优化程序设计
- 声明
Interface List<T> 和Class TestGen<K, V>其中T, K, V不代表值,而是表示类型,泛型可以使用任意字母表示,当在开发过程中通常是E或T表示[大写]泛型.
- 泛型类需要注意点:
a) 对象实例化事不指定泛型,默认是Object
b) 泛型不同的引用不能相互赋值
c) 加入集合中的对象类型必须与指定的泛型类型一致
d) 静态方法中不能使用类的泛型
e) 如果泛型类是一个接口或抽象类,则不可创建泛型类的对象
f) 不能在catch块中使用泛型
g) 从泛型类派生子类,泛型类型必须要具体化
- 4. 泛型方法
//泛型方法[如果方法中传递的参数是泛型,则方法必须声明为泛型方法]
public static <E> void printArray(E[] inputArrys){
//输出数组
for (E entryStr : inputArrys) {
System.out.print(entryStr+" ");
}
}
- 通配符
1) 类型通配符:? //只读型
Eg:List<?>, Map<?,?>
List<?>表示是list<String>, list<Object>等各种泛型List的父类
只读型的泛型类不能写入[添加]元素,因为不知道泛型具体的类型,所有添加元素时会报错
- 有限制的通配符
Eg:
<? extends Number>:(无穷小,Number]表示只允许泛型为number及number子类的引用调用
<? Super Number>:[Number, 无穷大)表示只允许泛型为number及number父类的引用调用
<? Extends Comparable>:只允许泛型为实现Comparable接口的实现类的引用调用
//求总数的方法
public static <E>void sumList(List<? extends Number> list){
double sum=0;
Iterator<? extends Number> it = list.iterator();
while(it.hasNext()){
sum += it.next().doubleValue();
}
System.out.println(sum);
}
//求最大数
public static double getMax(Collection<? extends Number> coll){
Iterator<? extends Number > it = coll.iterator();
if(!it.hasNext()){
throw new RuntimeException("集合为空!!!");
}
Number maxNumber = it.next(); //假设第一个数最大
while(it.hasNext()){
Number tempNumber = it.next();
if(tempNumber.doubleValue()>maxNumber.doubleValue()){
maxNumber = tempNumber;
}
}
return maxNumber.doubleValue();
}