第九章 java集合
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
- 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 属于临时性的比较