第九章 java集合
JCF集合框架
Collection单值类型集合:接口
Map键值对类型集合:接口
List有序不唯一
ArrayList
Vector
Stack
LinkedList
Set无序唯一
SortedSet有序唯一
HashSet
LinkedHashSet
HashMap
LinkedHashMap
TreeMap
Hashtable
Properties
1. Iterator迭代器
- 如何创建迭代器
Iterator<类型> i = list.iterator();
- 使用迭代器遍历集合
for(Iterator<类型> i = list.iterator();i.hasNext();){
}
//for循环不通用
//foreach遍历集合的同时删除集合里边的元素,会报错CME 并发修改异常
//所以需要用迭代器自己的remove()方法
1.1迭代器方法
- 如何创建一个迭代器
Iterator<泛型> car = list.iterator();
//<>里边的泛型一定要和集合的泛型保持一致
- hasNext()
判断集合是不是还有下一个元素、
- next()
取出下一个元素
- remove()
删除集合里边的元素
2. List
2.1 ArrayList
- 如何创建集合对象
//jdk5.0以前 默认装Object[]
ArrayList list = new Array();
//jdk5.0之后 可以加泛型
ArrayList<String> list = new ArrayList<String>();
//7.0 后边的泛型可以省略
Array<Integer> list = new ArrayList<>();
- 如何往集合添加元素
//一次添加一个元素
list.add(44);
list.add(45);
//一次添加多个元素 集合名字 元素
Collections.addAll(list,22,22,33,4,5,5);
- 如何得到集合的大小
list.size();
- 如何得到某一个元素
list.get(下标);
- 判断集合是否包含一个元素
list.contains(元素);
- 如何遍历集合
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
for(Integer x: list){
System.out.println(x);
}
- 使用迭代器遍历集合
//hadNext()判断迭代器后边是否还有元素
for(Iterator<泛型> iter = list.iterator;car.hasNext();){
//next()去除先一个元素
car.next();
}
2.1.1 ArrayList常用方法
- remove()
remove方法有两个,一个传下标,另外一个传元素,判断两个元素是不是一样的看元素底层的equals()方法。==具体看传入的参数的equals();==传入的参数会主动调用自己的equals()方法和集合里的每一个对象作比较
//一次只可以删除一个对象
- contains()
比较集合是否包含指定元素也是用的equals
- clear()
清空集合用clear();
面试题
Collection 和Collections 的区别
Collection 是所有单值类型集合的父接口 :interface
Collections 是集合的工具类 :class
2.1.2 ArrayList拓展🐴
- 集合扩容
当创建一个数组对象的时候需要确定数组的大小
ArrayList底层是基于Object[]数组实现的,集合里边存几个元素时根据ArrayList的构造方法决定的
①ArrayList list = new ArrayList(100);传入多少就开辟多少
②ArrayList list = new ArrayList();不传参数,底层默认开辟10块空间
集合会自己扩容,所以不用担心不够用
JDK6.0及以前 x * 3 / 2 + 1
JDK7.0及以后 x + (x >> 1 )
//把集合扩容到指定的空间
list.ensureCapacity(300);
//减少集合空间
list.trimToSize();
- 手写集合
public class Exec1{
public static void main(String[] args){
AList list = new AList();
list.add("123");
list.add("456");
list.add(666);
System.out.println(list.size());
System.out.println(list.contains(new Integer(666)));
list.remove(0);
list.add("999");
System.out.println(list);
list.remove("999");
System.out.println(list);
//前后都要加泛型
AList<Integer> list1= new AList<>();
CollectionsTest.addAll(list1,123,123,234,543,7657);
System.out.println(list);
AList<Teacher> t = new AList<>();
t.add("赵信");
System.out.println(t);
t.remove("赵信");
System.out.println(t.size());
}
}
class CollectionsTest{
public static void addAll(AList<Integer> list,Integer ... obj){
for(Integer data: obj){
list.add(data);
}
}
}
class AList<E>{
//数组用来存放元素
private Object[] data;
//元素个数
private int size;
//有参构造方法,用户传进来集合大小
public AList(int x){
if(x < 0){
System.out.println("ArrayIndexOutOfBoundsException:" + x);
}
data = new Object[x];
}
//无参构造方法,默认为10
public AList(){
this(10);
}
//得到集合大小
public int size(){
return size;
}
//得到元素
public Object get(int x){
return data[x];
}
//添加元素
public void add(Object obj){
//判断如果集合满了,就进行扩容
if(data.length == size){
Object[] temp = new Object[size + (size >> 1)];
System.arraycopy(data,0,temp,0,size);
data = temp;
}
data[size] = obj;
size++;
}
//删除元素,按照下标进行删除
public void remove(int x){
/*
删除指定下标的元素,相当于把指定下标以后的元素复制到指定下标处,
复制完成之后元素个数减一
*/
System.arraycopy(data,x + 1,data,x,size - x - 1);
size--;
}
//删除元素,按照指定元素删除
public void remove(Object obj){
for(int i = 0; i < size;i++){
//挨个遍历数组,找到一样的就删除吊
if(obj.equals(data[i])){
remove(i);
}
}
}
//判断集合里边是否包含指定元素
public boolean contains(Object obj){
if(obj == null) return false;
for(int i = 0;i < size;i++){
if(obj.equals(data[i])){
return true;
}
}
return false;
}
@Override
public String toString(){
String str = "[";
for(int i = 0; i < size -1;i++){
str = str + data[i] + ",";
}
return str + data[size - 1] + "]";
}
}
class Teacher{
String name
public Teacher(String name){
this.name = name;
}
@Override
public String toString(){
return name;
}
}
- addAll()
ArrayList<Integer> list = new ArrayList<>();
ArrayList<Integer> test = new ArrayList<>();
Collectoins.addAll(list,1,2,3,4,5,6);
//直接把集合塞到另外一个集合里边
test.addAll(list);
2.2 Vector
- 语法和ArrayList一模一样
面试题
Vector 和ArrayList的区别
- 同步线程不同
Vector同一时间允许一个线程进行访问,效率较低但是不会出现并发错误
ArrayList同一时间允许多个线程进行访问,效率较高,但是可能会出现并发错误
从JDk5.0之后
- 扩容机制不同
ArrayList:分版本
JDK6.0及以前 x * 3 / 2 + 1
JDK7.0及以后 x + (x >> 1)
Vetor:分构造方法
Vetor(10) -> 2倍扩容 10 - 20 -30
Vetor(10,3) -> 定长扩容 10 -13 -16
- 出现版本不同
可答可不答
Vetor : 1.0
ArrayList: 1.2
2.3 LinkedList
面试题
LinkedList和ArrayList之间的区别
- LinkedList和ArrayList的底层数据结构不同,导致优劣不同
ArrayList | LinkedList | |
底层数据结构 | 数组 | 链表 |
优点 | 随机查找、遍历较快 | 添加删除元素效率较高 |
缺点 | 添加删除元素效率低 | 随机查找、遍历效率慢 |
2.4 Stack
- 采用栈结构,先进后出
- 添加元素
Stack<Integer> list = new Stack<>();
list.push(666);
- 拉出元素
System.out.println(list.pop());
3. Set
Set集合修改元素的步骤
public class RemoveTest{
public static void main(String[] args){
Set<Integer> set = new HashSet<>();
Collections.addAll(set,11,22,33,44,55);
//1.创意一个临时的 同类型集合
Set<Integer> temp = new HashSet<>();
for(Iterator<Integer> car = set.iterator();car.hasNext();){
if(car.next() == 55){
//2.删除原有的元素
car.remove();
//3。吧修改后的元素放到临时集合中
temp.add(45);
}
}
//4.把修改之后的元素放回老集合中
set.addAll(temp);
}
}
3.1 HashSet
- 没有顺序
- 相同的元素只能添加一次
- 所有涉及到下标的方法都没有了
- 基于哈希表实现
HashSet的用法与ArrayList的用法基本一样但是所有跟下标有关的方法都不可以使用了
包含get()、remove()、for()遍历集合
- 如何创建对象
HashSet<Integer> set = HashSet<>();
- 遍历集合
for(Integer x : set){
System.out.println(x);
}
for(Iterator<Iterger> car = set.iterator();car.hasNext();){
System.out.println(set.next());
}
HashSet的唯一性
唯一:内存里的同一个对象,不会添加多次
“唯一”:将两个不同的对象视为相等的对象取决于***hashCode()*** 和***equals()***
HashSet会根据传入对象的hashCode()得到的哈希码来决定具体分到哪一个组,如果两个对量的哈希码值是一样的话,才会调用equals来判断两个对象是不是一个对象,如果equals返回两个对象是一个对象的话,就不可以重复添加
当两个对象的哈希码值一样的时候,有3 种情况
①内存里的同一个对象、不可以重复添加
②视为相等对象、会调用equals()方法来是不是同一个对象
③重码
适用的方法有 add()、remove()、contains();
拓展
- 当有重复元素的时候,会抛弃新元素。老元素留着
- 当一个元素已经添加进HashSet集合的时候,不要随意修改参与生成hashCode()值的属性,如果一定要修改,要先删除后修改再添加
3.1.1 HashSet常用方法
- addAll
import java.util.*;
public class Exec1{
public static void main(String[] args){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张三","李四","李四","张三","王五");
HashSet<String> e = new HashSet<>();
//将另外一个集合里边所有的东西撞到HashSet里边
e.addAll(list);
System.out.println(e);
System.out.println(e.size());
}
}
3.2 LinkedHashSet
- 用法和HashSet一样
- 作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
- 优点:对于频繁的便利操作,优先考虑LinkedHashSet
3.3 TreeSet
- 可以按照添加对象的指定属性进行排序
- 向 TreeSet 中添加数据,要求是**相同类的对象**
- 与 TreeMap 一样采用红黑树的存储结构
- 有序,查询速度比List块
- compareTo方法一样的返回0
在使用TreeSet的时候应该尽量保证compareTo方法有可能返回0
否则Tree Set集合的 add 方法用于不会认定有重复元素,无法保证唯一
同时TreeSet的 remove 方法也不乏删除元素 ,
add()、remove()、contains()、都是以来与compareTo()返回值是0的
如果需求就是compareTo方法就是无法返回0的,必须借助迭代器的删除方法
class User implements Comparable{
private String name;
private int age;
//get set 方法
public User(String name,int age){
this.name = name;
this.age = age;
}
//重写hashCode 和 equals 方法
@Override
public int compareTo(Object o){
if(o instanceof User){
User u = (User)o;
return this.getName().compareTo(u.getName());
}else{
throw new RuntimeException("输入的类型有误");
}
}
}
class TreeSetTest{
@Test
public void test(){
TreeSet<User> t = new TreeSet<>();
t.add("Tom",22);
t.add("Lee",20);
t.add("Wangwu",18);
}
}
3.3.1 TreeSet的遍历方式
//for Iterator
//lambda表达式
set.forEach(System.out::println);
set.forEach((x) -> System.out.println(x);)
3.3.2得到TreeSet的第一个和最后一个元素
//得到第一个元素
set.first();
//做后一个元素
set.last();
//pollFirst()选出并移除第一个元素
4. Map
Map
HashMap
LinkedHashMap
TreeMap
Hashtable
Properties
- key 是不可以重复的,相当于用 Set 存储的,唯一无序
- value 是可以重复的,无序
4.1 HashMap
- HashMap 作为 Map 的 主要实现类,线程不安全,效率较高,可以存储 null 的 key 和 value
- Hashtable 作为原始的实现类,线程安全,效率低,不能存储 null 的 key 和 value
public class MapTest{
public static void main(String[] args){
Map map = new HashMap();
}
}
默认容量是 16
默认加载因子是 0.75 也就是扩容的临界值是12
4.1.1常用方法
- put() 添加
//添加元素
map.put("AA",123);
- put() 修改
//修改元素
map.put("AA",1234);
- putAll()
//将一个map添加到另外一个map中
Map m = new HashMap();
m.putAll(map);
- remove() 移除
//remove(Object key)移除参数放key
System.out.println(map.remove("CC"));
- clear() 清空数据
map.clear();
- get() 获取指定 key 的value
System.out.println(map.get("AA"));
//当没有填入的参数的时候返回null
- containsKey / containsValue
//判断当前map是否包含指定的key
System.out.println(map.containsKey("AA"));//true
//判断当前map是否包含指定的key
System.out.println(map.containsValue(123));//true
- isEmpty() 判断当前map是否为空
System.out.println(map.isEmpty());
对元数据的操作
- keySet() 遍历map里所有的key
Set set = map.keySet();
for(Iterator i = set.iterator();i.hasNext()){
System.out.println(i.next());
}
- values() 遍历map里所有的value
Collection c = map.values();
for(Object o : c){
System.out.println(c);
}
- entrySet() 遍历所有的 key - value
Set e = map.entrySet();
for(Iterator i = e.iterator();i.hasNext();){
//entrySet 集合中的元素都是 entry
Map.Entry e = (Map.Entry)(i.next());
System.out.println(e.getKey() + "--" + e.getValue());
}
- getKey()
//获取记录的键
System.out.println(e.getKey());
- getValue()
//获取对应的值
System.out.println(e.getValue());
- setValue()
//修改值
e.setValue();
无论我们使用keySet()、values()、entrySet()所得到的的都不是一个新的集合
12.lambda表达式遍历Map集合
map.forEach((k,v) -> System.out.println(k));
map.forEach((k,v) -> System.out.println(v));
map.forEach((k,v) -> System.out.println(k + ":" + v));
面试题:HashMap的底层实现原理
JDK 7以前
HashMap map = new HashMap();
- 在实例化以后,底层创建了一个长度为16的一维数组 Entry[] table
map.put(key1,value1);
- 调用 key1 所在类的 hashCode() 计算哈希值,在一定处理后(& 15),用来确定在Entry 数组中的存放位置。
- 如果此位置上没有数据,此时 key1 - value1 添加成功(添加的是value),
- 如果该位置有位置,意味着此位置有一个或多个数据,比较 key1 和已经存在的 key 的哈希值
如果 key1 的哈希值与已经存在的数据的哈希值都不相同,就添加成功
如果和以及存在的某一个相同,就判断equals,返回 false 就添加成功 返回 true 使用value 1 替换相同 key 的value值
JDK8底层实现有所不同
- new HashMap() : 底层没有创建一个长度为16的数组
- JDK 8 底层的数组是 Node[] 而非 Entry[]
- 首次调用 put() 方法,底层创建长度为 16 的数组
- JDK 7 底层结构只有数组 + 链表 JDK 8底层数据结构:数组 + 链表 + 红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前的数组长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储
4.2 LinkedHashMap
- 保证在遍历map元素时,可以按照添加的顺序实现遍历。
- 在原有的 HashMap 的底层结构基础上,添加了一对指针,指向前一个和后一个元素。
- 对于频繁的遍历操作,执行效率高于 HashMap
@Test
public void test(){
Map map = new LinkedHashMap();
map.put(1,"AA");
map.put(1,"BB");
map.put(1,"CC");
System.out.println(map);
Map map1 = new HashMap();
map1.putAll(map);
System.out.println(map1);
}
4.3 TreeMap
- 保证按照添加的 key - value 对进行排序,实现排序遍历
- 按照 key 进行排序,要求key必须是同一个类创建的对象
class Test{
@Test
public void test(){
TreeMap map = new TreeMap();
map.put(new User("Tom",23),98);
map.put(new User("Jerry",21),91);
map.put(new User("Jack",20),78);
map.put(new User("Rose",22),58);
//compareTo自然排序
Set e = map.entrySet();
for(Iterator car = e.iterator();car.hasNext();){
Map.Entry et = (Map.Entry)(car.next());
System.out.println(et.getKey() + "--" + et.getValue());
}
}
@Test
public void t2(){
TreeMap map = new TreeMap(new Comparator(){
@Override
public int compare(Object o1,Object o2){
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compareTo(u1.getAge(),u2.getAge());
}
}
});
}
}
class User implements Comparable{
private String name;
private int age;
public User(String name,int age){
this.name = new name;
this.age = new age;
}
//重写 toString(),equals(),compareTo()方法
}
4.4 Hashtable
4.4.1 Properties
- 常用来处理配置文件, key 和 value 都是 String 类型
public class PropretiesTest{
Propreties pros = new Propreties();
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);
String name = pros.getProperty("name");
String pwd = pros.getProperty("password");
}
//配置文件
/*
name=zhaojinhui
password=123456
*/
配置文件出现中文乱码解决方法
Setting – > File Encodings --> √ Transparent native-to-ascii conversion
5. Collections 工具类
- 是一个操作**Collection 和 Map **的工具类
5.1 常用方法
方法 | 作用 |
reverse(List) | 反转List中元素的顺序 |
shuffle(List) | 对List进行随机排序 |
sort(List) | 根据元素自然顺序队List里元素升序排列 |
sort(List,Comparator) | 根据指定的比较规则排序 |
swap(List,int i,int j) | 将List集合 i 处和 j 处元素交换 |
方法 | 作用 |
max(Collection) | 根据自然顺序,返回最大值 |
max(Collection,Comparator) | 根据定制顺序返回最大值 |
min(Collection) | 根据自然顺序返回最小值 |
min(Collection,Comparator) | 根据定制顺序返回最小值 |
frequency(Collection,Object) | 返回指定元素出现的次数 |
copy(List i,List j) | 将 j 中的内容复制到 i 中 |
replaceAll(List i,Object o,Object n) | 用 n 替换 i 集合里边的 o |
public class Test{
public static void main(String[] args){
ArrayList<Integer> arr = new ArrayList<>();
Collections.addAll(arr,1,2,3,4,5);
List dest = Arrays.asList(new Object[arr.size()]);
System.out.println(dest.size());
}
}
- Collections 类提供了多个synchronizedXxx() 方法,该方法可以将制定集合包装成线程同步的集合,从而解决多线程并发访问时产生的线程安全问题除了Vector (Stack) Hashtable,其他的集合都是线程不安全的
- 在多线性高并发的情况下应该是用ConcurrentHashMap
List oldList = new ArrayList();
List newList = Collections.synchronizedList(oldLiset);
6. Java比较器
6.1 Comparable接口
自然排序 Comparable接口的使用
- 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个队想的方法
- 重写compareTo(obj) 的规则:
如果当前对象 this 大于形参对象 obj ,则返回正整数
小于,返回负整数
等于,返回0 - 对于自定义类,如果需要排序,可以让自定义类实现Comparable接口,重写compareTo(obj)在方法中指明如何排序
public class CompareTest{
@Test
public void test(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovo",34);
arr[1] = new Goods("huawei",65);
arr[2] = new Goods("dell",14);
arr[3] = new Goods("xiaomi",43);
arr[4] = new Goods("ausu",43);
Arrays.sort(arr);
}
}
class Goods implements Comparable{
String name;
double price;
public Goods(String name,double price){
this.name = name;
this.price = price;
}
//指明按照什么方式排序
@Override
public int compareTo(Object o){
if(o instanceof Goods){
Goods g = (Goods)o;
if(this.price > g.price){
return 1;
}else if(this.price < g.price){
return -1;
}else{
return this.name.copareTo(g.name);
}
}
throw new RunTimeException("传入的数据类型不一致!");
}
}
6.2 Comparator接口
定制排序
- **当元素的类型没有实现Comparable接口而有不方便修改代码,或者实现了Comparable接口排序规则不适合当前的操作,name可以考虑使用Camparator排序,**强行队多个对象进行整体排序的比较
- 可以将Comparator传递到 sort 方法,从而允许在排序上实现精确控制(如Collections.sort 或Arrays.sort)
@Test
public void test(){
Arrays.sort(arr,new Comparator<Goods>(){
//按照产品名称从低到高
@Override
public int compare(Goods i,Goods j){
if(i instanceof Goods && j instanceof Goods){
Goods g1 = (Goods)i;
Goods g2 = (Goods)j;
if(g1.getName().equals(g2.getName())){
return Double.compare(g1.getPrice(),g2.getPrice());
}else{
return g1.getName().compareTo(g2.getName());
}
}
}
});
}
测试类
@Data
public class Test implements Comparator<Test>{
private Integer orders;
}
Lambda式简化版1
import iava.util.*;
public class TestComparator{
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
Collections.addAll(list,22,33,77,14,44,55,66);
Collections.sort(list,(x,y) -> x - y);
}
}
Lambda简化版2
import iava.util.*;
public class TestComparator{
public static void main(String[] args){
List<Test> list = new ArrayList<>();
COllections.addAll(list, ...);
Comparator<Test> comp = Comparator.comparing(Test::getOrders);
Collections.sort(list,comp);
}
Lambda不当人简化版简化版3
list.sort(Comparator.comparing(Test::getOrders));
6.3 两种接口的对比
- Comparable 接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置都可以比较大小
- Comparator 属于临时性的比较