第九章 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迭代器方法

  1. 如何创建一个迭代器
Iterator<泛型> car = list.iterator();
 //<>里边的泛型一定要和集合的泛型保持一致
  1. hasNext()

判断集合是不是还有下一个元素、

  1. next()

取出下一个元素

  1. remove()

删除集合里边的元素

2. List

2.1 ArrayList

  1. 如何创建集合对象
//jdk5.0以前 默认装Object[]
ArrayList list = new Array();
//jdk5.0之后 可以加泛型
ArrayList<String> list = new ArrayList<String>();
//7.0 后边的泛型可以省略
Array<Integer> list = new ArrayList<>();
  1. 如何往集合添加元素
//一次添加一个元素
list.add(44);
list.add(45);
//一次添加多个元素    集合名字  元素
Collections.addAll(list,22,22,33,4,5,5);
  1. 如何得到集合的大小
list.size();
  1. 如何得到某一个元素
list.get(下标);
  1. 判断集合是否包含一个元素
list.contains(元素);
  1. 如何遍历集合
for(int i = 0;i < list.size();i++){
    System.out.println(list.get(i));
}
for(Integer x: list){
    System.out.println(x);
}
  1. 使用迭代器遍历集合
//hadNext()判断迭代器后边是否还有元素
for(Iterator<泛型> iter = list.iterator;car.hasNext();){
    //next()去除先一个元素
    car.next();
}
2.1.1 ArrayList常用方法
  • remove()

remove方法有两个,一个传下标,另外一个传元素,判断两个元素是不是一样的看元素底层的equals()方法。==具体看传入的参数的equals();==传入的参数会主动调用自己的equals()方法和集合里的每一个对象作比较

//一次只可以删除一个对象
  • contains()

比较集合是否包含指定元素也是用的equals

  • clear()

清空集合用clear();

面试题

CollectionCollections 的区别

Collection 是所有单值类型集合的父接口 :interface

Collections 是集合的工具类 :class

2.1.2 ArrayList拓展🐴
  1. 集合扩容

当创建一个数组对象的时候需要确定数组的大小

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();
  1. 手写集合
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;
	}
}
  1. 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一模一样

面试题

VectorArrayList的区别

  1. 同步线程不同

Vector同一时间允许一个线程进行访问,效率较低但是不会出现并发错误

ArrayList同一时间允许多个线程进行访问,效率较高,但是可能会出现并发错误

从JDk5.0之后

  1. 扩容机制不同

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

  1. 出现版本不同可答可不答

Vetor : 1.0

ArrayList: 1.2

2.3 LinkedList

面试题

LinkedListArrayList之间的区别

  1. LinkedList和ArrayList的底层数据结构不同,导致优劣不同

ArrayList

LinkedList

底层数据结构

数组

链表

优点

随机查找、遍历较快

添加删除元素效率较高

缺点

添加删除元素效率低

随机查找、遍历效率慢

2.4 Stack

  • 采用栈结构,先进后出
  1. 添加元素
Stack<Integer> list = new Stack<>();
list.push(666);
  1. 拉出元素
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()遍历集合

  1. 如何创建对象
HashSet<Integer> set = HashSet<>();
  1. 遍历集合
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();

拓展

  1. 当有重复元素的时候,会抛弃新元素。老元素留着
  2. 当一个元素已经添加进HashSet集合的时候,不要随意修改参与生成hashCode()值的属性,如果一定要修改,要先删除后修改再添加

3.1.1 HashSet常用方法
  1. 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常用方法
  1. put() 添加
//添加元素
map.put("AA",123);
  1. put() 修改
//修改元素
map.put("AA",1234);
  1. putAll()
//将一个map添加到另外一个map中
Map m = new HashMap();
m.putAll(map);
  1. remove() 移除
//remove(Object key)移除参数放key
System.out.println(map.remove("CC"));
  1. clear() 清空数据
map.clear();
  1. get() 获取指定 key 的value
System.out.println(map.get("AA"));
//当没有填入的参数的时候返回null
  1. containsKey / containsValue
//判断当前map是否包含指定的key
System.out.println(map.containsKey("AA"));//true
//判断当前map是否包含指定的key
System.out.println(map.containsValue(123));//true
  1. isEmpty() 判断当前map是否为空
System.out.println(map.isEmpty());

对元数据的操作

  1. keySet() 遍历map里所有的key
Set set = map.keySet();
for(Iterator i = set.iterator();i.hasNext()){
    System.out.println(i.next());
}
  1. values() 遍历map里所有的value
Collection c = map.values();
for(Object o : c){
    System.out.println(c);
}
  1. 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());
}
  1. getKey()
//获取记录的键
System.out.println(e.getKey());
  1. getValue()
//获取对应的值
System.out.println(e.getValue());
  1. 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();
  1. 在实例化以后,底层创建了一个长度为16的一维数组 Entry[] table
map.put(key1,value1);
  1. 调用 key1 所在类的 hashCode() 计算哈希值,在一定处理后(& 15),用来确定在Entry 数组中的存放位置。
  2. 如果此位置上没有数据,此时 key1 - value1 添加成功(添加的是value),
  3. 如果该位置有位置,意味着此位置有一个或多个数据,比较 key1 和已经存在的 key 的哈希值
    如果 key1 的哈希值与已经存在的数据的哈希值都不相同,就添加成功
    如果和以及存在的某一个相同,就判断equals,返回 false 就添加成功 返回 true 使用value 1 替换相同 key 的value值

JDK8底层实现有所不同

  1. new HashMap() : 底层没有创建一个长度为16的数组
  2. JDK 8 底层的数组是 Node[] 而非 Entry[]
  3. 首次调用 put() 方法,底层创建长度为 16 的数组
  4. 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接口

定制排序

  1. **当元素的类型没有实现Comparable接口而有不方便修改代码,或者实现了Comparable接口排序规则不适合当前的操作,name可以考虑使用Camparator排序,**强行队多个对象进行整体排序的比较
  2. 可以将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 属于临时性的比较