集合类
1、前言
先通过一个例子来引入:
public static void main(String[] args) {
// 数组
int[] nums = {1, 4, 4, 5, 67, 34 };
// 集合
List list = new ArrayList<>();
list.add("ycz");
list.add(25);
list.add(new Date());
System.out.println("数组元素如下:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + "\t");
}
System.out.println();
System.out.println("----------------------------");
System.out.println("集合中元素如下:");
for (Object obj : list) {
System.out.print(obj.toString() + "\t");
}
}
控制台输出:
从这个例子来看集合和数组的区别:
- 数组一旦确定,长度是不可变的,而集合长度可以改变。
- 数组中存的是同种类型数据,集合中可存不同类型数据,一般不建议这样做,会加泛型进行限制,只存同类型。
集合可以看做是一个容器,容器中可以存放任何种对象,可以将元素存进去,也能取出来,此外,还可以进行遍历、添加、删除、查找等操作。集合在Java中是非常常见的,因此,熟练掌握集合的运用非常重要。
2、集合框架
集合的框架如下图:
将以上图进行简化:
这里面有直接继承和间接继承的。
3、Collection接口
Collection接口是所有集合的根接口。List接口、Set接口、Map接口都直接继承自Collection接口。这个接口通常不能直接使用,提供了元素的添加、删除、管理的等方法,因此这些方法对于它的子接口都是通用的。api文档说明如下:
所有方法如下:
常用方法说明如下:
- add(E e):添加元素,返回布尔值。
- addAll(Collection c):将另一个集合的所有元素添加进来,返回布尔值。
- clear():清空集合中的元素,这个方法不返回。
- contains(Object o):集合中是否包含某元素,返回布尔值。
- isEmpty():判断集合是否为空,返回布尔值。
- iterator():返回集合的元素迭代器,返回Iterator类型。
- remove(Object o):删除集合的指定元素,返回布尔值。
- removeAll(Collection c):从当前集合中删除指定集合中的全部元素,返回布尔值。
- retainAll(Collection c):只保留指定集合中的所有元素,其他的元素删掉,返回布尔值。
- size():获取集合中的元素数,即集合大小,返回int类型。
- toArray():集合转换为数组,返回Object类型数组。
以下在代码中测试:
public static void main(String[] args) {
// Collection接口是不能自身实例化的
Collection<String> c = new ArrayList<>();
c.add("凡人歌");
c.add("雪中情");
c.add("痴心绝对");
c.add("一剪梅");
// 获取集合的迭代器
Iterator<String> it = c.iterator();
// 遍历
System.out.println("集合中的所有元素如下:");
//hasNext方法判断是否有元素
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
Collection<String> c2 = new ArrayList<>();
c2.add("伤心太平洋");
c2.add("红尘有你");
// 将c2集合中的元素添加到c
c.addAll(c2);
System.out.println("----------------------");
System.out.println("集合中的所有元素如下:");
Iterator<String> it2 = c.iterator();
while (it2.hasNext()) {
String s = it2.next();
System.out.println(s);
}
System.out.println("集合中的元素个数:" + c.size());
System.out.println("集合中有一剪梅这首歌吗?" + c.contains("一剪梅"));
if (c.contains("一剪梅")) {
c.remove("一剪梅");
}
System.out.println("集合中的元素个数:" + c.size());
// 删除指定集合中的全部元素
c.removeAll(c2);
System.out.println("集合中的元素个数:" + c.size());
// 集合转为数组
Object[] strs = c.toArray();
System.out.println("数组中的元素如下:");
for (int i = 0; i < strs.length; i++) {
System.out.print(strs[i].toString() + "\t");
}
System.out.println();
if (!c.isEmpty()) {
// 清空集合元素
c.clear();
}
System.out.println("集合中的元素个数:" + c.size());
}
控制台:
4、区分Collection和Collections
Collections是java.util包下的,它是一个工具类,这个类里面只提供静态方法,api文档说明如下:
具体方法可参考api文档的java.util.Collections 。
而Collection是集合的根接口,即Collection是接口,Collections是工具类。
5、List集合
List集合包括List接口和List接口的所有实现类。List集合中允许元素重复,各个元素在集合中的顺序就是添加时的顺序,类似于数组,可以通过索引来获取集合中的元素。
5.1、List接口
List接口直接继承自Collection接口,因此包含Collection接口中提供的所有方法,除此之外,List接口还定义下如下几种常用的方法:
- add(int index,E e):将指定元素插入到指定位置,返回布尔值。
- get(int index):获取指定索引位置的元素。
- indexOf(Object o):返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
- lastIndexOf(Object o):返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
- set(int index, E e):用指定的元素替换此列表中指定位置的元素。
- sort(Comparator<? super E> c):集合中的元素排序。
- subList(int fromIndex, int toIndex):返回此列表中指定的 fromIndex (含)和 toIndex之间的视图,返回的类型是List。
List接口有ArrayList、LinkedList、Vector等实现类。
5.2、ArrayList实现类
ArrayList类实现了可变数组,允许保存所有元素,包括null,可以根据索引位置对集合中的元素进行快速的随机访问,优点是访问快,缺点是向指定的索引插入元素或删除元素很慢。
api文档说明如下:
源码:
以下用例子说明。
定义一个实体类:
public class Student {
private String name;
private String sex;
public Student(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
测试:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 获取ArrayList类实例
List<Student> stus = new ArrayList<>();
System.out.println("开始录入信息!");
for (int i = 1; i <= 3; i++) {
System.out.println("开始录入第" + i + "个人的信息:");
System.out.println("请输入第" + i + "个人的姓名:");
String name = in.next();
System.out.println("请输入第" + i + "个人的性别:");
String sex = in.next();
Student s = new Student(name, sex);
stus.add(s);
}
// 可以通过get方法取集合元素
for (int i = 0; i < stus.size(); i++) {
System.out.println("姓名:" + stus.get(i).getName() + ",性别:" + stus.get(i).getSex());
}
System.out.println("-------------------------");
// foreach遍历更加方便
for (Student s : stus) {
System.out.println("姓名:" + s.getName() + ",性别:" + s.getSex());
}
System.out.println("--------------------------");
// 也可以通过迭代器遍历
Iterator<Student> it = stus.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println("姓名:" + s.getName() + ",性别:" + s.getSex());
}
}
控制台:
再测试一下其他的方法:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 10; i > 0; i--) {
list.add(i);
}
System.out.println("集合中的元素个数:" + list.size());
System.out.println("集合中所有元素:");
for (int i : list) {
System.out.print(i + "\t");
}
System.out.println();
// 默认排序器排序
Collections.sort(list);
System.out.println("------------------------------------------");
System.out.println("集合中所有元素:");
for (int i : list) {
System.out.print(i + "\t");
}
System.out.println();
System.out.println("8的索引位置位置是:" + list.indexOf(8));
// 替换元素
list.set(7, 888);
System.out.println("索引位7的元素是:" + list.get(7));
// 集合元素截取,包头不包尾
List<Integer> list2 = list.subList(2, 6);
System.out.println("集合中的元素个数:" + list2.size());
System.out.println("集合中所有元素:");
for (int i : list2) {
System.out.print(i + "\t");
}
}
控制台:
5.3、LinkedList实现类
LinkedList类采用链表结构保存对象。这种结构的优点是便于向集合中插入和删除对象,增删操作比ArrayList效率高。但是对于随机访问,链表结构很慢,所以LinkedList的查询速度比ArrayList慢很多。
api文档说明:
源码:
代码测试:
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
list.add(11);
list.add(22);
list.add(33);
long t1 = System.currentTimeMillis();
for (int i = 100000; i > 0; i--) {
list.add(2, i);
}
long t2 = System.currentTimeMillis();
System.out.println("LinkedList添加元素耗时为:" + (t2 - t1) + "ms");
List<Integer> list2 = new ArrayList<>();
list2.add(11);
list2.add(22);
list2.add(33);
long t3 = System.currentTimeMillis();
for (int i = 100000; i > 0; i--) {
list2.add(2, i);
}
long t4 = System.currentTimeMillis();
System.out.println("ArrayList添加元素耗时为:" + (t4 - t3) + "ms");
}
控制台:
可以看到,向指定位置插入元素,LinkedList比ArrayList的效率高的不是一点半点。
5.4、Vector实现类
Vector集合也被称为项量集合,一种类似于ArrayList的可变动态数组集合,区别是Vector是线程同步的,所以较ArrayList集合数据处理效率低,除了具有ArrayList的所有方法外,还增加了public Enumerationelement()方法,返回枚举接口,可实现对集合的变量。
api文档说明如下:
源码:
可以看到Vector类中的很多方法都加了synchronized关键字,所以Vector是线程安全的。
代码测试:
public static void main(String[] args) {
List<Student> v = new Vector<>();
Student s0 = new Student("无极剑圣", "男");
Student s2 = new Student("疾风剑豪", "男");
Student s3 = new Student("放逐之刃", "女");
Student s4 = new Student("迅捷斥候", "男");
Student s5 = new Student("影流之主", "男");
v.add(s0);
v.add(s3);
v.add(s5);
v.add(s2);
v.add(s4);
for (Student s : v) {
System.out.print(s.getName() + "\t" + s.getSex() + "\n");
}
}
控制台:
6、Set集合
Set集合包含Set接口和Set接口的所有实现类。Set集合中的对象不按特定的方式排序,也就是说是无序的,只是将对象加到集合中,并不保证与添加的顺序完全一致,Set集合中不能包含重复的元素。
6.1、Set接口
Set接口直接继承自Collection接口,包含Collection接口提供的所有方法。api文档说明如下:
Set接口的实现类有HashSet、LinkedHashSet、TreeSet等。
6.2、HashSet实现类
HashSet类由哈希表提供支持,实际上是一个HashMap的实例。不保证Set集合迭代顺序,特别是不保证顺序不变,此类支持使用null元素。api文档说明如下:
源码:
以下用实例说明。
创建一个实体类Good:
public class Good {
private String goodId;// 商品ID
private String goodName;// 商品名称
private double price;// 商品单价
private int number;// 商品数量
public Good(String goodId, String goodName, double price, int number) {
this.goodId = goodId;
this.goodName = goodName;
this.price = price;
this.number = number;
}
public String getGoodId() {
return goodId;
}
public void setGoodId(String goodId) {
this.goodId = goodId;
}
public String getGoodName() {
return goodName;
}
public void setGoodName(String goodName) {
this.goodName = goodName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
测试:
public static void main(String[] args) {
Set<Good> goods = new HashSet<>();
Good g0 = new Good("可口可乐", "yl1004", 2.5, 150);
Good g2 = new Good("冰红茶", "yl1002", 3.0, 200);
Good g3 = new Good("柠檬VITA", "yl1005", 3.5, 300);
Good g4 = new Good("雀巢咖啡", "yl1007", 5.5, 100);
Good g5 = new Good("安慕希", "yl1001", 6.5, 280);
goods.add(g0);
goods.add(g2);
goods.add(g3);
goods.add(g4);
goods.add(g5);
System.out.println("商品信息如下:");
for(Good g:goods) {
System.out.print(g.getGoodId() + "--------" + g.getGoodName() + "---------"
+ g.getPrice() + "--------" + g.getNumber() + "\n");
}
}
控制台:
可以很明显的看到,元素在集合中的顺序和添加时候的顺序是不一致的,这也正是Set集合的特点,不保证顺序永久不变。
6.3、TreeSet实现类
TreeSet类不仅实现了Set接口,还实现了java.util.SortedSet接口。因此,TreeSet类实现的Set集合在遍历时按照默认的顺序排序,也就是升序。也可以按照自定义的排序器Comparator进行排序。因为要排序,效率比HashSet稍低。api文档说明如下:
源码:
测试:
public static void main(String[] args) {
Set<Good> goods = new TreeSet<>(new Comparator<Good>() {
/*
* 按照价格由高到低排序
*/
@Override
public int compare(Good o1, Good o2) {
if(o1.getPrice() > o2.getPrice()) {
return -1;
} else if(o1.getPrice() < o2.getPrice()) {
return 1;
} else {
return 0;
}
}
});
Good g0 = new Good("可口可乐", "yl1004", 2.5, 150);
Good g2 = new Good("冰红茶", "yl1002", 3.0, 200);
Good g3 = new Good("柠檬VITA", "yl1005", 3.5, 300);
Good g4 = new Good("雀巢咖啡", "yl1007", 5.5, 100);
Good g5 = new Good("安慕希", "yl1001", 6.5, 280);
goods.add(g0);
goods.add(g2);
goods.add(g3);
goods.add(g4);
goods.add(g5);
System.out.println("商品信息如下:");
for(Good g:goods) {
System.out.print(g.getGoodId() + "--------" + g.getGoodName() + "---------"
+ g.getPrice() + "--------" + g.getNumber() + "\n");
}
}
控制台:
现在我想按照数量由低到高排序,只需修改排序器Comparator里面的compare排序规则即可:
Set<Good> goods = new TreeSet<>(new Comparator<Good>() {
/*
* 按照价格由高到低排序
*/
@Override
public int compare(Good o1, Good o2) {
if (o1.getNumber() > o2.getNumber()) {
return 1;
} else if (o1.getNumber() < o2.getNumber()) {
return -1;
} else {
return 0;
}
}
});
其余地方不变,执行,控制台:
确实是按数量低到高排序的。
除了有Collection接口的方法外,TreeSet类中还添加了很多其他的方法,这里只说常用的几种:
- first():返回集合中的第一个元素。
- lats():返回集合中的最后一个元素。
- comparator():返回集合中进行排序的排序器,如果是自然顺序,则返回null。
- headSet(E e):返回新的Set集合,是e之前的所有元素,不包括e。
- subSet(E from,E end):返回新的Set集合,是from元素到end元素之间的所有元素,不包括end。
- tailSet(E e):返回新的Set集合,是e之后的所有元素,包含e。
以下进行测试:
public static void main(String[] args) {
TreeSet<Good> goods = new TreeSet<>(new Comparator<Good>() {
/*
* 按照价格由高到低排序
*/
@Override
public int compare(Good o1, Good o2) {
if (o1.getNumber() > o2.getNumber()) {
return 1;
} else if (o1.getNumber() < o2.getNumber()) {
return -1;
} else {
return 0;
}
}
});
Good g0 = new Good("yl1004", "可口可乐", 2.5, 150);
Good g2 = new Good("yl1002", "冰红茶", 3.0, 200);
Good g3 = new Good("yl1005", "柠檬VITA", 3.5, 300);
Good g4 = new Good("yl1007", "雀巢咖啡", 5.5, 100);
Good g5 = new Good("yl1001", "安慕希", 6.5, 280);
// 将g4对象赋值给g6,Set集合是不能有重复元素的
// 如果重复添加,后面的会覆盖掉前面的
// 这里由于g4和g6一样,所以g4会被覆盖掉
Good g6 = g4;
g6.setGoodName("怡宝矿泉水");
g6.setGoodId("yl1010");
g6.setPrice(2.0);
g6.setNumber(120);
// g6会覆盖g4,尽管添加了6个元素,集合中实际上只有5个
goods.add(g0);
goods.add(g2);
goods.add(g3);
goods.add(g4);
goods.add(g5);
goods.add(g6);
System.out.println("商品信息如下:");
for (Good g : goods) {
System.out.print(g.getGoodId() + "--------" + g.getGoodName() + "---------" + g.getPrice() + "--------"
+ g.getNumber() + "\n");
}
System.out.println("-------------------------------------");
// 获取集合中的第一个元素
// 排序之后,第一个应该是矿泉水
Good one = goods.first();
System.out.println("第一件商品是:" + one.getGoodName());
// 获取集合中的最后一个元素
Good last = goods.last();
System.out.println("最后一件商品是:" + last.getGoodName());
System.out.println("-------------------------------------");
// 获取元素之前的元素
Set<Good> set = goods.headSet(g0);
System.out.println("取到的商品如下:");
for (Good g : set) {
System.out.print(g.getGoodId() + "--------" + g.getGoodName() + "---------" + g.getPrice() + "--------"
+ g.getNumber() + "\n");
}
System.out.println("-------------------------------------");
// 获取集合元素之间的元素
Set<Good> set2 = goods.subSet(g0, g5);
System.out.println("取到的商品如下:");
for (Good g : set2) {
System.out.print(g.getGoodId() + "--------" + g.getGoodName() + "---------" + g.getPrice() + "--------"
+ g.getNumber() + "\n");
}
System.out.println("-------------------------------------");
// 获取集合元素之后的元素
Set<Good> set3 = goods.tailSet(g5);
System.out.println("取到的商品如下:");
for (Good g : set3) {
System.out.print(g.getGoodId() + "--------" + g.getGoodName() + "---------" + g.getPrice() + "--------"
+ g.getNumber() + "\n");
}
System.out.println("-------------------------------------");
// 获取当前集合使用的排序器
Comparator comparator = goods.comparator();
System.out.println(comparator);
System.out.println("-------------------------------------");
//这里没有自定义排序器Comparator,则使用默认的
TreeSet<Integer> tree = new TreeSet();
tree.add(20);
tree.add(10);
tree.add(50);
tree.add(70);
tree.add(5);
tree.add(15);
// 如果不重写Comparator下的Compare方法,则默认的排序是升序的
for (int i : tree) {
System.out.print(i + "\t");
}
System.out.println();
// 获取当前集合排序器,默认的会返回null
Comparator comparator2 = tree.comparator();
System.out.println(comparator2);
}
控制台:
6.4、LinkedHashSet实现类
基于哈希表和链接结构的Set接口实现类,此类是HashSet的直接子类,并且实现了Set接口。与HashSet无序集合相比,LinkedHashSet迭代是一个可被预知的访问操作,可以添加到集合中的顺序为迭代的最终顺序。因为要维护元素顺序,所以LinkedHashSet效率比HashSet要稍低,通常在保证使用Set接口并且按照顺序迭代时可使用此类。
api文档中说明如下:
源码:
构造方法:
以下通过一个例子来说明。
定义一个实体类:
public class Province {
private String name;// 省名称
private double area;// 区域面积
private int general;// 区域性质,0代表普通省,1代表直辖市
public Province(String name, double area, int general) {
this.name = name;
this.area = area;
this.general = general;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getArea() {
return area;
}
public void setArea(double area) {
this.area = area;
}
public int getGeneral() {
return general;
}
public void setGeneral(int general) {
this.general = general;
}
}
然后定义一个工具类:
/*
* 工具类
*/
public class ProvinceManager {
private LinkedHashSet<Province> provinces;
public ProvinceManager() {
if (provinces == null) {
provinces = new LinkedHashSet<>();
}
}
public LinkedHashSet<Province> getProvinces() {
return provinces;
}
// 定义一个添加元素的方法
public void addProvince(Province p) {
provinces.add(p);
}
// 定义一个方法,显示所有省份信息
public void showAll() {
for (Province p : provinces) {
System.out.print(p.getName() + "\t" + p.getArea() + "\t" + p.getGeneral() + "\n");
}
}
// 定义一个方法,按照省名获取省份
public Province getProvince(String name) {
for (Province p : provinces) {
if (p.getName().equals(name)) {
return p;
}
}
return null;
}
}
测试:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
ProvinceManager pm = new ProvinceManager();
System.out.println("开始录入省份信息!");
for (int i = 1; i <= 5; i++) {
System.out.println("开始录入第" + i + "个省份的信息:");
System.out.println("请输入第" + i + "个省的名称:");
String name = in.next();
System.out.println("请输入第" + i + "个省的区域面积:");
double area = in.nextDouble();
System.out.println("请输入第" + i + "个省是省还是直辖市?");
System.out.println("普通省份请输入0");
System.out.println("直辖市请输入1");
int general = in.nextInt();
Province p = new Province(name, area, general);
pm.addProvince(p);
}
System.out.println("---------------------------------------");
System.out.println("所有省份信息如下:");
pm.showAll();
boolean tag = true;
do {
System.out.println("---------------------------------------");
System.out.println("请输入要查找的省份名称:");
String res = in.next();
System.out.println("---------------------------------------");
Province province = pm.getProvince(res);
if (province == null) {
System.out.println("无当前省份信息,请重新输入!");
} else {
tag = false;
if (province.getGeneral() == 0) {
System.out.println("省份名称:" + province.getName() + ",区域面积:" + province.getArea() + ",性质:普通省份");
} else if (province.getGeneral() == 1) {
System.out.println("省份名称:" + province.getName() + ",区域面积:" + province.getArea() + ",性质:直辖市");
}
}
} while (tag);
}
执行,控制台:
7、Map集合
Map集合包括Map接口和Map接口的所有实现类。注意一点,就是Map接口并没有继承Collection接口,这点要注意。Map集合提供key到value的映射,Map集合中不能包含相同的key,每个key只能映射一个value。对于相同的key,如果添加新的value,则会覆盖掉原来的value。key决定了存储对象在映射中的存储位置,但并不是由key本身决定的,而是通过散列技术进行处理,产生一个散列码整数值,散列码通常用作一个偏移量,该偏移量对应分配给映射的内存区域的起始位置,从而来确定存储对象在映射中的存储位置。
7.1、Map接口
Map接口提供了将key映射到值的对象。一个映射不能包相同的key,每个key最多只能映射到一个值。Map接口的实现类有HashMap、TreeMap、Hashtable等。api文档说明如下:
Map接口提供的方法如下:
下面对一些常用方法进行说明:
- clear():清除Map集合中的元素。
- containsKey(Object key):集合中是否包含指定的key,返回布尔值。
- containsValue(Object value):集合中是否包含指定的value,返回布尔值。
- entrySet():返回此集合中包含的映射的Set集合,即返回Set<Map.Entry<K,V>>类型。
- get(Object key):获取key对应的value值。
- isEmpty():判断集合是否为空,返回布尔值。
- keySet():返回集合中所有key组成的Set集合,即返回一个Set集合。
- put(K key, V value):往集合中存键值对,存的是key和value的映射关系。
- putAll(Map m):将集合m中的所有元素添加到当前集合中。
- remove(Object key):从集合中按照指定的key移除key到value的映射关。
- remove(Object key, Object value):仅当指定的密钥当前映射到指定的值时删除该条目。
- replace(K key, V value):只有当目标映射到某个值时,才能替换指定键的条目。
- size() :集合中的键值映射数量,返回int。
- values():返回此集合中所有value组成的Collection视图,即返回Collection对象。
先在代码中测试这些常用方法:
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "湖北");
map.put("2", "湖南");
map.put("3", "江西");
map.put("4", "四川");
map.put("5", "重庆");
map.put("6", "广东");
Map<String, String> map2 = new HashMap<>();
map2.put("7", "广西");
map2.put("8", "浙江");
map2.put("9", "陕西");
System.out.println("集合是否为空?" + map.isEmpty());
System.out.println("集合中存的映射关系数量:" + map.size());
System.out.println("集合中是否包含3这个key?" + map.containsKey("3"));
System.out.println("集合中是否包含四川这个value?" + map.containsValue("四川"));
System.out.println("5这个key对应的value是:" + map.get("5"));
// 获取所有的key
Set<String> set = map.keySet();
System.out.println("所有的key如下:");
for (String s : set) {
System.out.print(s + "\t");
}
System.out.println();
// 获取所有的value
Collection<String> c = map.values();
// 获取迭代器
Iterator<String> it = c.iterator();
System.out.println("所有的value如下:");
while (it.hasNext()) {
String s = it.next();
System.out.print(s + "\t");
}
System.out.println();
// 获取所有的key到value的映射
Set<Entry<String, String>> set2 = map.entrySet();
System.out.println("所有的key-value映射如下:");
for (Entry<String, String> entry : set2) {
System.out.println(entry.getKey() + "==========>" + entry.getValue());
}
// 将map2加到map中
map.putAll(map2);
System.out.println("map集合中存的映射数量:" + map.size());
// 移除一个映射
map.remove("9");
System.out.println("map集合中存的映射数量:" + map.size());
System.out.println("2这个key对应的value是:" + map.get("2"));
// 替换掉某个key的值
map.replace("2", "湖南2号");
System.out.println("2这个key对应的value是:" + map.get("2"));
// 清除集合中的所有映射关系
map.clear();
System.out.println("map集合中存的映射数量:" + map.size());
}
执行,控制台:
2、HashMap实现类
HashMap类由哈希表提供支持。此类提供所有可选的映射操作,允许使用null键和null值,不保证映射顺序,非线程安全,如果在多线程中使用,必选要处理同步问题。HashMap实现类可以使用Map接口提供的所有方法。
api文档说明如下:
源码如下:
以下通过一个例子说明。
定义一个实体类Car:
public class Car {
private String brand;// 汽车品牌
private double power;// 马力
private double speed;// 速度
private Date createDate;// 出厂日期
public Car(String brand, double power, double speed, Date createDate) {
this.brand = brand;
this.power = power;
this.speed = speed;
this.createDate = createDate;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPower() {
return power;
}
public void setPower(double power) {
this.power = power;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
}
测试:
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(2002, 6, 10);
Date d0 = calendar.getTime();
Car c0 = new Car("哈弗", 3, 60, d0);
calendar.set(2005, 5, 10);
Date d2 = calendar.getTime();
Car c2 = new Car("奔驰", 5, 120, d2);
calendar.set(2009, 8, 19);
Date d3 = calendar.getTime();
Car c3 = new Car("传祺", 4, 108, d3);
calendar.set(2012, 10, 7);
Date d4 = calendar.getTime();
Car c4 = new Car("一汽大众", 6, 120, d4);
// c4会被c5覆盖掉
Car c5 = c4;
calendar.set(2018, 11, 7);
Date d5 = calendar.getTime();
c5.setBrand("本田");
c5.setPower(5.5);
c5.setSpeed(100);
c5.setCreateDate(d5);
Map<String, Car> map = new HashMap<>();
map.put("1000", c0);
map.put("2000", c2);
map.put("3000", c3);
map.put("4000", c4);
map.put(null, null);
// 获取所有key
Set<String> set = map.keySet();
System.out.println("所有key如下:");
for (String s : set) {
System.out.print(s + "\t");
}
System.out.println();
System.out.println("------------------------------------------------------");
// 获取所有value
Collection<Car> col = map.values();
Iterator<Car> it = col.iterator();
System.out.println("所有汽车品牌如下:");
while (it.hasNext()) {
Car c = it.next();
if (c == null) {
System.out.println(c);
} else {
System.out.print(c.getBrand() + "\t");
}
}
System.out.println();
System.out.println("------------------------------------------------------");
Set<Entry<String, Car>> set2 = map.entrySet();
for (Entry<String, Car> entry : set2) {
if (entry.getKey() != null) {
System.out.print(entry.getKey() + "=====>" + entry.getValue().getBrand() + "\n");
}
}
}
执行,控制台:
3、TreeMap实现类
TreeMap类不仅实现了Map接口,还实现了java.util.SortedMap接口,集合中的映射关系具有一定的顺序,默认以键为依据采用自然顺序进行排序,或者根据创建映射时提供的Comparator排序器进行排序,因此不允许key为null,value可以为null。效率较HashMap低,非线程安全。
api文档说明如下:
源码:
修改上面的代码,如下:
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(2002, 6, 10);
Date d0 = calendar.getTime();
Car c0 = new Car("哈弗", 3, 60, d0);
calendar.set(2005, 5, 10);
Date d2 = calendar.getTime();
Car c2 = new Car("奔驰", 5, 120, d2);
calendar.set(2009, 8, 19);
Date d3 = calendar.getTime();
Car c3 = new Car("传祺", 4, 108, d3);
calendar.set(2012, 10, 7);
Date d4 = calendar.getTime();
Car c4 = new Car("一汽大众", 6, 120, d4);
// c4会被c5覆盖掉
Car c5 = c4;
calendar.set(2018, 11, 7);
Date d5 = calendar.getTime();
c5.setBrand("本田");
c5.setPower(5.5);
c5.setSpeed(100);
c5.setCreateDate(d5);
TreeMap<String, Car> map = new TreeMap<String, Car>(new Comparator<Object>() {
// 按照键倒序排序
@Override
public int compare(Object o1, Object o2) {
Integer s = Integer.parseInt(o1.toString());
Integer s2 = Integer.parseInt(o2.toString());
if (s.intValue() > s2.intValue()) {
return -1;
} else if (s.intValue() > s2.intValue()) {
return 1;
} else {
return 0;
}
}
});
map.put("1000", c0);
map.put("2000", c2);
map.put("3000", c3);
map.put("4000", c4);
// 获取所有key
Set<String> set = map.keySet();
System.out.println("所有key如下:");
for (String s : set) {
System.out.print(s + "\t");
}
System.out.println();
System.out.println("------------------------------------------------------");
// 获取所有value
Collection<Car> col = map.values();
Iterator<Car> it = col.iterator();
System.out.println("所有汽车品牌如下:");
while (it.hasNext()) {
Car c = it.next();
System.out.print(c.getBrand() + "\t");
}
System.out.println();
System.out.println("------------------------------------------------------");
Set<Entry<String, Car>> set2 = map.entrySet();
for (Entry<String, Car> entry : set2) {
System.out.print(entry.getKey() + "=====>" + entry.getValue().getBrand() + "\n");
}
}
执行,控制台:
4、Hashtable实现类
任何非null对象都可作为键和值,与HashMap基本相同,唯一区别是Hashtable是线程同步的,而HashMap不是,所以在多线程中建议使用Hashtable,非多线程中建议使用HashMap。由于要处理多线程问题,所以Hashtable效率较HashMap低。
api文档说明如下:
源码:
很多方法上加了synchronized关键字,保证了线程同步。
8、ArrayList和LinkedList区别
这个在面试中经常问到,所以注意理解,先分别阐述这两个实现类各自的特点,最后做比较。
8.1、ArrayList
ArrayList是一个可改变大小的动态数组,其大小随元素的增加会自动增长。可以通过索引访问,每一个ArrayList都有一个初始容量为0,第一次添加元素时,会将元素容量扩增置10,每次添加元素时会进行容量检查,当容量快溢出时,会进行扩容。如果明确要插入的元素数量,最好是指定一个容量,可以避免多次扩容而影响效率。ArrayList擅长于随机访问,缺点是向指定位置插入、删除元素时较慢,不同步,数组在内存中是一块连续的区域,插入删除元素时需要移动内存,因此效率低。
8.2、LinkedList
是一个双向链表结构。指定位置增删元素时比ArrayList快,随机访问时慢于ArrayList,不同步。链表不要求内存连续,在当前元素位置放下一个或上一个元素的地址,查询要从头部开始一个个找,因此效率非常低。插入删除时不需要移动内存,只需要改变引用的指向即可。LinkedList擅长于指定位置增删元素,缺点是访问很慢,不同步。
8.3、比较
- ArrayList基于动态数组结构,LinkedList基于链表。
- 对于随机访问,ArrayList优于LinkedList,因为后者要移动指针。
- 对于指定位置增删元素,LinkedList优于ArrayList,因为后者要移动内存。
9、扩展
可以自行扩展关于动态数组和双向链表这两种数据结构的特点和实现原理。