第16章 集合类
1. 集合的概念
集合是一种数学概念,表述的是“由一个或多个确定的元素所构成的整体”。
1.1 Java中集合与数组的比较
数组
- 数组在初始化时是定长的,初始化后数组长度不可改变。(长度表示的是最大容量)
- 数组中只能存放同类型的数据,比如int数组只能存数字不能存String
- 数组中没有现成API方法使用,数组只有一个length属性可用
集合
- 集合在初始化时可以不设置长度,随着元素放入集合,长度可变。(集合中已经存放了多少个元素)
- 集合在没有约束泛型的情况下,其可以存放任意类型的数据
- 集合类中提供了大量的API方法,方便程序开发者操作集合
1.2 集合的分类
集合的继承体系- Collection接口(线性集合)
(1) List接口(有序号可重复)
ArrayList实现类基于数组方式实现
LinkedList实现类基于链表方式实现
(2) Set接口(无序号不重复)
HashSet实现类基于Hash表算法
- Map接口(键值集合)
HashMap实现类基于Hash表算法
- Iterator接口(迭代器)
2. 线性集合(Collection接口)
2.1 线性集合的概念
Collection接口表述的是“线性集合”,线性结构是n个数据元素的有序(次序)集合
线性集合的特征
1.集合中必存在唯一的一个"第一个元素"。
2.集合中必存在唯一的一个"最后的元素"。
3.除最后元素之外,其它数据元素均有唯一的"后继"。
4.除第一元素之外,其它数据元素均有唯一的"前驱"。
2.2 线性集合(Collection)的实现类
2.2.1 List接口
有序集合(也称为序列 )。 该界面的用户可以精确控制列表中每个元素的插入位置。 用户可以通过整数索引(下标)访问元素,并搜索列表中的元素。允许集合中存放重复的数据。
有序号可重复
2.2.1.1 ArrayList实现类
底层是基于数组实现的,方法都是线程不安全的
API方法
- add(Object o)
返回值是boolean
在集合的结尾处添加一个元素 - add(int index, Object o)
返回值是void
在index位置插入一个元素,插入后,后面的元素会自动后移 - addAll(Collection c)
返回值是boolean
在集合结尾处添加集合c中的所有元素 - clear()
返回值是void
清空集合,保留集合对象 - contains(Object o)
返回值是boolean
在集合中查找o是否存在,存在判断依据是元素是否“equals”o
如果在集合中找到了“相等”的元素,返回true,否则返回false - get(int index)
返回值是Object
获得集合中index位置上的元素,不会移除这个元素 - set(int index, Object o)
返回值是Object
用指定元素o替换集合中index位置上的元素,并返回被替代的元素 - remove(int index)
返回值是Object
移除集合中index位置上的元素,并返回这个元素 - remove(Object o)
返回值是boolean
移除集合中的第一个o元素(判断依据是集合中的某个元素“equals”o),返回是否移除成功。 - size()
返回值是int
返回集合中元素的个数(集合的长度) - indexOf(Object o)
返回值是Object
查找o在集合中位置,如果没找到返回-1
public class Test {
public static void main(String[] args) {
ArrayList list = new ArrayList();
System.out.println(list.size());
list.add(new String("哈哈"));
list.add(23);
list.add(new Date());
list.add("哈哈");
list.add(3.14);
System.out.println(list.size());
System.out.println("插入了5个元素==========================");
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("在2位置插入了嘻嘻==========================");
list.add(2, "嘻嘻");
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("替换2位置为嘿嘿==========================");
list.set(2, "嘿嘿");
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("移除位置2的元素==========================");
list.remove(2);
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("移除元素“3.14”的元素==========================");
list.remove(3.14);
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("移除元素“哈哈”的元素==========================");
list.remove(new String("哈哈"));
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("判断集合中是否有“哈哈”的元素==========================");
boolean b = list.contains("哈哈");
System.out.println(b);
System.out.println("判断集合中是否有“3.14”的元素==========================");
boolean x = list.contains(3.14);
System.out.println(x);
System.out.println("清空集合==========================");
list.clear();
System.out.println(list.size());
}
}
如果遇到了集合中有元素2,且还需要移除2下标时,注意写法
list.remove(2); //移除下标位置2的元素
list.remove(new Integer(2)); //移除集合中元素2
2.2.1.2 LinkedList实现类
底层是基于链表实现的
主要的增删改查的方法参照ArrayList类的API学习
特有API方法
- addFirst(Object o)和addLast(Object o)
返回值void
在集合的开始和结尾处插入一个元素o - getFirst()和getLast()
返回值Object
返回集合的开始和结尾处的元素
将LinkedList看做为一个栈
- peek()
返回值Object
查看栈顶元素,但不移除它 - poll()和pop()
返回值Object
出栈 - push(Object o)
返回值void
将Object o入栈
public class Test3 {
public static void main(String[] args) {
LinkedList<Object> list = new LinkedList<Object>();
list.push("哈哈");
list.push("嘻嘻");
list.push(100);
list.push(3.14);
list.push(new Date());
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("查看栈顶==============================");
Object x = list.peek(); //查看栈顶
System.out.println(x);
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("出栈==============================");
Object y = list.pop();
System.out.println(y);
System.out.println("====");
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("入栈==============================");
list.push("abc");
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
System.out.println("出栈==============================");
Object z = list.poll();
System.out.println(z);
System.out.println("====");
for(int i = 0; i < list.size(); i++) {
Object o = list.get(i);
System.out.println(o);
}
}
}
2.2.1.3 Vector实现类
底层是基于数组实现的,方法都是线程安全的
API方法参照ArrayList学习
笔试题/面试题
(1)ArrayList和LinkedList的区别?
本质上就是问“数组”与“链表”的区别。
ArrayList基于数组实现的,LinkedList基于链表实现
数组:在查询元素速度快,插入删除速度慢
链表:在查询元素速度慢,插入删除速度快
(2)ArrayList和Vector的区别?
ArrayList线程不安全,Vector线程安全
2.2.2 Set接口
不包含重复元素的集合。如果有元素e1和e2(e1.equals(e2) 为true),e1和e2只能保留一个元素。Set接口的特征是使用者无法控制元素的存放顺序,其存放顺序有Set接口的实现类自身的算法决定
2.2.2.1 HashSet实现类
实现算法是基于Hash表算法,存放顺序不由使用者控制,允许存放一个null值
Object类中有hashcode方法,返回int值,该int值是由对象的内存地址计算出来的
一个对象会有一个HashCode(哈希码,Hash码,HashCode),它有如下特征:
- 如果两个对象拥有相同的内存地址,他们一定有相同的哈希码
-
如果两个对象拥有相同的哈希码,他们的内存地址可能不一样
哈希表算法,按照哈希码的大小顺序存放数据。
比如:字符串“哈哈”的哈希码是34,字符串“嘻嘻”的哈希码是20。
使用代码存入集合的顺序:哈哈,嘻嘻
实际集合中存储顺序:嘻嘻,哈哈
比如:字符串“哈哈”的哈希码是34,字符串“嘻嘻”的哈希码是20,字符串“嘿嘿”的哈希码也是34。
使用代码存入集合的顺序:哈哈,嘻嘻
实际集合中存储顺序:嘻嘻,(哈哈,嘿嘿)
类似的存储结构
API方法
- add(Object o)
返回值boolean
将o对象放入集合,放入的位置由集合的算法自身决定 - clear()
返回值void
清空集合 - isEmpty()
返回值boolean
判断集合是否为空 - remove(Object o)
返回值boolean
在集合中移除o元素 - size()
返回值int
返回集合的长度 - iterator()
返回值Iterator
返回set集合对应的迭代器
因为Set集合无法使用下标,所以不能通过下标进行遍历访问集合中的元素。只能通过迭代器去遍历Set集合
2.2.3 Iterator(迭代器)
负责通过一个向前的“游标”不断的访问元素和移动,完成Set集合的遍历。
理解迭代器
API方法
- hasNext()
返回值:boolean
判断游标当前位置之后是否有数据 - next()
返回值:Object
取得游标当前位置之后的数据,再将游标向后移动一位
由于next()包含了移动的操作,所以在循环中应该只调用一次
示例:利用迭代器遍历set集合
public class Test1 {
public static void main(String[] args) {
HashSet<Object> set = new HashSet<Object>();
set.add("哈哈");
set.add(3.14);
set.add(new Date());
set.add("hehe");
set.add(23);
set.add(12);
System.out.println(set.size());
Iterator<Object> ite = set.iterator();//获得了set集合对应的迭代器
while(ite.hasNext()) {
Object o = ite.next();
System.out.println(o);
}
}
}
面试题/笔试题:
(1)List集合和Set集合的区别?
List集合:有序可重复,可以通过下标访问数据,存放顺序可控,允许存放重复数据
Set集合:无序不重复,不能通过下标访问数据,存放数据由实现类自己决定,重复数据只能保留一个
(2)HashSet和TreeSet的区别?
HashSet基于哈希表算法实现
TreeSet基于红黑树算法实现
很多时候使用Set集合完成去重复的操作。
3. 键值集合(Map接口)
Map集合是一种以键值对为单位,进行数据存储的一种结构,它与线性集合有所区别:线性集合一个位置上只存储一个数据,键值对集合一个位置上存储的是一个键(key)和一个值(value)。不同数据之间键(key)是不能重复的,值(value)是也可重复的。存放数据时,必须提供key和value,取得数据时一般是根据key来获取value
Map的主要实现类
3.1 HashMap和HashTable
基于哈希表算法实现存储
3.1.1 构造方法
- HashMap()
创建一个初始容量是16,负载因子是0.75的默认的对象
初始容量:对象初始默认可以存放16个键值对
负载因子:表示当容量达到最大容量的75%时(比如,初始16,16的75%是12),,容量会加倍扩容(16*2=32)。
这个构造方法是比较常用的 - HashMap(int init)
创建一个初始容量是init,负载因子是0.75的对象
- HashMap(int init, float ext)
创建一个初始容量是init,负载因子是ext的对象
- HashMap(Map map)
利用map的数据重新构建一个同样数据的HashMap
3.1.2 API方法
- put(Object key, Object value)
返回值:Object
将key-value组成的键值对,放入集合;如果集合中已经存在了同key的键值对,会利用新的key-value替换原有的key-value
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(101, "赵四"); //向集合中添加一条数据
map.put(102, "刘能"); //向集合中添加一条数据
map.put(101, "哈哈"); //替换集合中原来101这条数据
- remove(Object key)
返回值:Object
根据key值,删除集合中的一个键值对,并返回对应value
这个方法是使用频率较高的
map.remove(103);
- remove(Object key, Object value)
返回值:boolean
根据key-value值,删除集合中的一个键值对。返回是否删除成功
map.remove(104,"刘能"); //成功,有这对数据
map.remove(102,"aaa");//失败,虽然有102,102中不是aaa
- clear()
返回值:void
清空整个集合
map.clear();
- get(Object key)
返回值:Object
根据key值,返回集合中key对应value,如果集合中没有这个key,返回null
String v1 = map.get(103);
String v2 = map.get(105);
- size()
返回值:int
返回集合中键值对的个数
System.out.println("集合中的元素个数是:"+map.size());
- entrySet()
返回值:Set<Entry>
一个entry对象表示一个键值对
返回所有键值对组成的一个Set集合
Set<Entry<Integer, String>> set = map.entrySet();//得到键值对的Set集合
Iterator<Entry<Integer, String>> ite = set.iterator();//将set集合变成迭代器
while(ite.hasNext()) { //遍历迭代器
Entry<Integer, String> e = ite.next(); //一个键值对
System.out.println(e.getKey()+":"+e.getValue());
}
- keySet()
返回值:Set<Object>
返回所有key值组成的一个Set集合
Set<Integer> kset = map.keySet(); //所有key值组成的set集合
Iterator<Integer> kite = kset.iterator(); //将set集合变成迭代器
while(kite.hasNext()) { //遍历迭代器
int key = kite.next();
String value = map.get(key); //取得value
System.out.println(key+":"+value);
}
- isEmpty()
返回值:boolean
判断集合是否为空集合
boolean x = map.isEmpty();
- containsKey(Object key)
返回值boolean
判断集合中的key是否包含参数key,(判断依据:equals)
boolean b1 = map.containsKey(101);
boolean b2 = map.containsKey(105);
- containsValue(Object value)
返回值boolean
判断集合中的value是否包含参数value,(判断依据:equals)
boolean b3 = map.containsValue("刘能");
boolean b4 = map.containsValue("AAA");
面试题/笔试题
(1)HashMap与HashTable的区别
HashMap是线程不安全的,HashTable是线程安全。
HashMap中允许null作为key,HashTable不允许null作为key
(2)HashMap与TreeMap的区别
HashMap底层基于Hash表算法
TreeMap底层基于二叉树算法
(3)HashMap的桶容量(扩容机制)
以默认hashmap()构造方法解释:,默认初始值16,负载因子0.75.
初始时hashmap的最大容量是16,当存储数据至12时(16*0.75),进行扩容,扩容方式是16*2=32; 当继续存储数据至24时(32*0.75),继续扩容,扩容方式32*2=64
3.2 TreeMap
基于红黑树算法实现存储
参照HashMap的API去理解
- put
- get
- remove
- keySet
- entrySet
- containsKey
4. 泛型
泛型是用于约束集合中输入数据类型的。集合默认是可以将任何数据都放入其中,很多情况下我们需要约束集合中输入的元素类型,借助于泛型可以达到效果。
泛型的优势
- 约束输入至集合的数据类型,控制数据准确
- 从集合输出数据时,无需考虑类型转换问题
- 可以使用for...each句式快速遍历集合
约束输入类型为String
ArrayList<String> list = new ArrayList<String>();
由于泛型约束只支持引用数据类型,如果需要约束一个整数集合,需要使用包装类
ArrayList<Integer> list = new ArrayList<Integer>();
泛型约束Employee类的集合
ArrayList<Employee> list = new ArrayList<Employee>();
泛型约束其他的集合
ArrayList<HashSet<String>> list = new ArrayList<HashSet<String>>();
for...each句式:使用变量s去遍历list集合中每(each)一个元素
ArrayList<String> list = new ArrayList<String>();
for(String s : list) {
System.out.println(s);
}
5. Collections类(集合工具类)
主要提供了大量的关于集合操作的方法,方便操作集合
- copy(List dest, List src)
返回值:void
将集合src复制到集合dest中
list2一开始必须有不少于list1的数据,否则会报出异常
public class Test2 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<Integer>();
ArrayList<Integer> list2 = new ArrayList<Integer>();
Random r = new Random();
for(int i = 0; i < 10; i++) {
int x = r.nextInt(100);
list1.add(x);
}
for(int i = 0; i < 10; i++) {
int x = r.nextInt(100);
list2.add(x);
}
System.out.println(list1);
System.out.println(list2);
System.out.println("=======================");
//集合copy
Collections.copy(list2, list1);
System.out.println(list1);
System.out.println(list2);
}
}
- sort(List list)
返回值:void
将集合按照“自然排序”(实际上就是升序)
public class Test3 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<Integer>();
Random r = new Random();
for(int i = 0; i < 10; i++) {
int x = r.nextInt(100);
list1.add(x);
}
System.out.println(list1);
Collections.sort(list1);//升序
System.out.println(list1);
}
}
如果想比较的是自己编写的类,可以通过让自己的类实现Comparable接口并完成CompareTo方法
Student类
public class Student implements Comparable<Student>{
private int sno;
private String sname;
public Student() {}
public Student(int sno, String sname) {
this.sno = sno;
this.sname = sname;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@Override
public int compareTo(Student o) {
if(this.sno < o.sno) {
return -1;
}else if(this.sno == o.sno) {
return 0;
}else {
return 1;
}
}
}
利用sort方法进行排序
public class Test {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
Student s1 = new Student(1002,"赵四");
Student s2 = new Student(1004,"刘能");
Student s3 = new Student(1001,"谢广坤");
Student s4 = new Student(1003,"王大拿");
list.add(s1);
list.add(s2);
list.add(s3);
list.add(s4);
for(Student s : list) {
System.out.println(s.getSno()+"\t"+s.getSname());
}
System.out.println("============================");
Collections.sort(list);
for(Student s : list) {
System.out.println(s.getSno()+"\t"+s.getSname());
}
}
}
- sort(List list, Comparator c)
返回值:void
将集合按照“ Comparator”的规则进行排序
Student类
public class Student{
private int sno;
private String sname;
public Student() {}
public Student(int sno, String sname) {
this.sno = sno;
this.sname = sname;
}
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
}
编写一个比较器
public class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
if(o1.getSno() < o2.getSno()) {
return -1;
}else if(o1.getSno() == o2.getSno()) {
return 0;
}else {
return 1;
}
}
}
利用sort进行排序
public class Test {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<Student>();
Student s1 = new Student(1002,"赵四");
Student s2 = new Student(1004,"刘能");
Student s3 = new Student(1001,"谢广坤");
Student s4 = new Student(1003,"王大拿");
list.add(s1);
list.add(s2);
list.add(s3);
list.add(s4);
for(Student s : list) {
System.out.println(s.getSno()+"\t"+s.getSname());
}
System.out.println("============================");
Collections.sort(list, new StudentComparator());
for(Student s : list) {
System.out.println(s.getSno()+"\t"+s.getSname());
}
}
}
- reverse(List list)
返回值:void
将集合list中的所有数据反转
public class Test3 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<Integer>();
Random r = new Random();
for(int i = 0; i < 10; i++) {
int x = r.nextInt(100);
list1.add(x);
}
System.out.println(list1);
Collections.sort(list1);//升序
System.out.println(list1);
Collections.reverse(list1);//降序
System.out.println(list1);
}
}