10、集合

120 阅读11分钟

一、概念和特点

集合是用于存储数据的容器

特点:

  1. 集合的长度是可变的

  2. 集合中只能存储引用类型

  3. 同一个集合中可以存储不同类型的元素,集合定义时需要明确集合的泛型,也就是集合中存储元素的类型,如果不指定泛型,那么可以存储的类型就是Object的任意类型。

    不指定泛型的好处:一个集合中元素的类型可以是任意的

    不指定泛型的弊端:存储到集合中的任何一个元素都是以多态的方式进行存储的,因此从集合中获取到的元素对象不能访问特有成员

集合的结构:

顶层接口:Collection

子接口:List、Set

List的实现类:ArrayList、LinkedList、Vector

Set的实现类:HashSet、TreeSet、LinkedHashSet

二、Collection中的方法

1.add(Object o): 在集合中添加指定元素
2.addAll(Collection c): 将c集合中的所有元素添加到集合中
3.remove(Object o): 删除集合中的指定元素
4.removeAll(Collection c): 删除集合中与c集合中所有相同的元素
5.size(): 获取集合的大小
6.clear(): 清空集合
7.contains(Object o): 判断集合中是否包含指定元素这个元素
8.containsAll(Collection c): 判断集合中是否包含c集合中的所有元素
9.equals(Collection c): 判断两个集合是否相等,考虑元素的顺序
10.isEmpty(): 判断是否为空

三、迭代器

迭代:迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次"迭代",而每一次迭代得到的结果会作为下一次迭代的初始值。

迭代的步骤:

  1. 通过集合对象获取迭代器对象

    Collection c1 = new ArrayList();
    Iterator it = c1.iterator();
    
  2. 迭代器中的方法

    1. hasNext():判断是否有下一个元素可以迭代,返回布尔值
    2. next():获取下一个元素
    Iterator it = c1.iterator();
    while(it.hasNext()){
        System.out.println(it.next());
    }
    

    foreach循环也可以用来遍历Collection集合,因为foreach的底层就是迭代器

注:

集合在遍历的过程,不允许增删,会发生ConcurrentModificationException,如何解决呢:

可以在遍历的过程调用迭代器的remove()方法进行删除

四、List接口

实现类:ArrayList、LinkedList、Vector

特点:

  1. 有序
  2. 可重复

4.1、ArrayList

常用方法:

1.add(int i,Object o): 在指定下标i处添加元素o
2.addAll(int i,Collection c): 在指定下标i处添加集合c中的所有元素
3.remove(int i): 删除下标为i的元素
4.set(int i,Object o): 将下标为i的元素修改为指定元素
5.get(int i): 获取下标为i的元素
6.indexOf(Object o): 获取指定元素的下标
7.lastIndexOf(Object o): 获取集合中该指定元素最后一次出现时的下标
/*示例:
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
System.out.println(list.lastIndexOf("a"));//4      
*/
8.retainAll(Collection c): 在集合中保留与c集合相同的元素
/*示例:
ArrayList<String> list1 = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
ArrayList list2 = new ArrayList();
list5.add("a");
list5.add("b");
list5.add("c");
list5.add("d");
list1.retainAll(list2);
System.out.println(list1);//[a,b,c,d]
*/

4.2、LinkedList

方法:

1.addFirst(Object o): 在集合的开始位置添加指定元素
2.addLast(Object o): 在集合的最后面添加指定元素
3.push(Object o): 将指定元素添加到开头位置
4.remove(): 获取集合中的第一个元素,并删除元素
5.removeFirst(): 获取集合中的第一个元素,并删除元素
6.removeLast(): 获取集合中的最后一个元素,并删除元素
7.pop(): 获取集合中的第一个元素,并删除元素
8.peek(): 获取集合中的第一个元素,不删除元素
9.peekFirst(): 获取集合中的第一个元素,不删除元素
10.peekLast(): 获取集合中的最后一个元素,不删除元素
11.poll(): 获取集合中的第一个元素,并删除元素
12.pollFirst(): 获取集合中的第一个元素,并删除元素
13.pollLast(): 获取集合中的最后一个元素,并删除元素
14.element(): 获取集合中的第一个元素
15.offer(Object o): 在集合的最后面添加指定元素
16.offerFirst(Object o): 在集合最前面添加指定元素
17.offerLast(Object o): 在集合的最后面添加指定元素
18.getFirst(): 获取集合中的第一个元素,不删除元素
19.getLast(): 获取集合中的最后一个元素,不删除元素

4.3、Vector

方法:

1.addElement("1"): 在集合末尾添加指定元素
2.firstElement(): 获取集合中的第一个元素
3.lastElement(): 获取集合中的最后一个元素
4.elementAt(int i): 获取集合中指定下标的元素
5.insertElementAt(Object o,int i): 在集合中的指定位置放入指定元素
6.removeAllElements(): 删除集合中的所有元素
7.removeElement(Object o): 删除集合中的指定元素
8.removeElementAt(int i): 删除集合中下标为i的指定元素
9.setElementAt(Object o,int i): 将集合中指定下标的元素修改为指定的新元素

Vector和ArrayList底层结构都是数组

  1. Vector:保证线程同步
  2. ArrayList:不保证线程同步

LinkedList和ArrayList:

  1. ArrayList底层是数组,查询快、增删慢
  2. LinkedList底层是链表,增删快、查询慢

五、Set接口

实现类:HashSet、TreeSet、LinkedHashSet

特点:

  1. 无序:存储顺序和获取顺序不一致,Set集合和它的任何一个实现类都没有与下标相关的方法
  2. 不可重复

5.1、HashSet

元素存储到HashSet的过程中经历了hashCode()和equals()的调用

存储原理:

先调用hashCode()方法获取这个元素的哈希值,判断在集合中是否已经有某一个元素的哈希值与这个元素的哈希值相同,如果没有则直接存储,如果有调用equals()方法,判断返回结果,如果结果是true,则不存储,如果结果是false,则存储。

如何保证HashSet中的对象唯一:重写hashCode()和equals()

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Stu stu = (Stu) o;
return age == stu.age && name.equals(stu.name);
}
​
@Override
public int hashCode() {
return Objects.hash(name, age);
}

5.2、TreeSet

能实现自动排序的Set集合

注:使用TreeSet将对象按照属性进行排序,需要使该类实现Comparable接口,重写compareTo()方法,明确集合是以什么属性进行升降序排列;如果要比较的属性差为0,那么相同属性值的第二个对象无法存储,因为TreeSet将指定相同属性值的对象视为同一个对象。

示例:
//按学生的年龄属性进行降序排列
public class Test5TreeSet {
 public static void main(String[] args) {
     Stu s1 = new Stu("张三",27);
     Stu s2 = new Stu("李四",45);
     Stu s3 = new Stu("王五",19);
     Stu s4 = new Stu("赵六",34);
     Stu s5 = new Stu("田七",56);
     TreeSet<Stu> ts = new TreeSet<>();
     ts.add(s1);
     ts.add(s2);
     ts.add(s3);
     ts.add(s4);
     ts.add(s5);
     Iterator<Stu> it = ts.iterator();
     while (it.hasNext()){
         System.out.println(it.next());
     }
 }
}
class Stu implements Comparable<Stu>{
 String name;
 int age;
 public Stu(){
​
 }
​
 public Stu(String name, int age) {
     this.name = name;
     this.age = age;
 }
​
 @Override
 public String toString() {
     return "Stu{" +
             "name='" + name + ''' +
             ", age=" + age +
             '}';
 }
​
 @Override
 public int compareTo(Stu o) {
     return o.age - this.age;
 }
}

5.3、LinkedHashSet

存取顺序一致的Set集合

LinkedHashSet存储的原理和HashSet相同,区别是LinkedHashSet多了一条用于保存存储顺序的链表,所以LinkedHashSet是存取顺序一致

LinkedHashSet也没有下标相关的方法,并且它也是不可重复的。

六、不同集合的底层

ArrayList:可变数组,不保证线程安全,效率高

扩容:

	1. 有参构造函数:创建后获得一个指定大小的数组,当添加到最大长度时,扩容为原来1.5倍

 		2. 无参构造函数:创建后获得一个空数组,第一次添加数据时扩容到10,当添加第11个元素时,扩容为原来的1.5倍

Vector:可变数组,安全,效率低

扩容:

  1. 有参构造函数:创建后获得一个指定长度的数组,当添加到最大长度时,扩容为原来的2倍
  2. 无参构造函数:创建后获得一个长度为10的数组,当添加第11个元素时,扩容为原来的2倍

LinkedList:

  1. 底层是双向链表
  2. 在LinkedList中定义了两个属性first、last分别指向首节点和尾节点
  3. LinkedList中的每一个元素都是一个Node对象,每一个Node对象包含三个属性,prev、next、item,prev指向前一个元素,next指向后一个元素,item用于保存值

HashSet:

  1. 创建时获取一个空数组
  2. 第一次添加元素时,数组扩容到16,临界值是12(数组大小*加载因子)
  3. 如果数组添加到临界值12,就会扩容为原来的2倍为32,新的临界值也扩容为原来的2倍为24

七、Collections

此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。

如果为此类的方法所提供的 collection 或类对象为 null,则这些方法都将抛出 NullPointerException

常用方法:

1.addAll(Collection c,Object...o): 在集合中添加元素,可以添加多个
/*示例:
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1,"1","张三","w","刘备");
System.out.println(list1);// [1, 张三, w, 刘备]
*/
2.copy(List l1,List l2): 将集和l2中的元素拷贝到集合l1中,集合l1的大小应当大于等于l2集合
/*示例:
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1,"1","张三","w","刘备");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2,"1","1","1","1","1");
Collections.copy(list2,list1);
System.out.println(list2);//[1, 张三, w, 刘备, 1]
*/
3.fill(List l,Object o): 将集合中的所有元素用指定元素替代
4.indexOfSubList(List l1,List l2): 获取集合l2中的所有元素的整体在集合l1中的位置下标
/*示例:
ArrayList<String> list3 = new ArrayList<>();
Collections.addAll(list3,"1","2","3","4","5","6");
ArrayList<String> list4 = new ArrayList<>();
Collections.addAll(list4,"2","3");
int i = Collections.indexOfSubList(list3,list4);
System.out.println(i);// 1
*/
5.max(Collection c): 求数字与字母集合中的最大值
6.min(Collection c): 求数字与字母集合中的最小值
7.max(Collection c,Comparator c): 求对象集合中的最大值,通过重写比较器将对象的属性值进行比较
8.min(Collection c,Comparator c): 求对象集合中的最小值,通过重写比较器将对象的属性值进行比较
9.replaceAll(List l, Object oldVal, Object newVal): 将集合中的指定元素用指定元素替换
10.reverse(List l): 将集合倒置
11.shuffle(List l): 打乱集合中的排列顺序
12.swap(List l,int i1,int i2): 交换两个指定下标元素的位置
13.sort(List l): 对集合中的元素进行排序
14.sort(List l,Comparator c): 将对象集合中的对象元素进行排序,通过重写比较器将对象的属性值进行比较

八、Map

  1. Map是双列集合,Map中的每一个元素都有键和值两个内容来确定的

    常用的实现类:

    1. HashMap
    2. TreeMap:根据键实现自动排序的Map集合
    3. Hashtable:线程同步的,HashMap不保证线程同步,HashMap中允许键和值是null值,Hashtable中的键或者值空了都会发生NullPointerException
    4. Properties

    特点:

    1. Map中的元素都是一个一个的键值对
    2. 键和值的类型是任意的
    3. 键不能重复,值可以重复
    4. Map集合没有下标

常用方法:

1.put(Object k,Object v): 添加指定的键值,键是不可以重复的,值可以重复,如果键相同,新值会替换掉旧值
/*示例:
  HashMap<String,String> map = new HashMap<>();
  map.put("A","a");
  map.put("B","b");
  map.put("C","c");
  map.put("A","1");
  System.out.println(map.put("B","2"))//b 返回被替换掉的值,没有重复的键被替换返回null
  System.out.println(map);{A = 1,B = 2,C = c}
*/
2.size(): 获取集合的大小
3.remove(Object k): 删除指定键的元素,键值对一起移除
4.remove(Object k,Object v): 删除指定键值对整体,如果集合中没有,则不删除
5.replace(Object k,Object newVal): 将指定键的值替换为新的值
6.replace(Object k,Object oldVal,Object newVal): 先判断键值对整体是否存在于集合中,在,用新值替换旧值,不在不做替换
7.get(Object k): 根据指定的键获取值
8.clear(): 清空集合
9.containsKey(Object k): 判断是否包含指定键
10.containsValue(Object k): 判断是否包含指定值
11.getOrDefault(Object k,Object defVal): 根据指定键能获取到值就获取此值,不能获取则给定一个指定值
12.values():返回一个当前Map集合所有值组成的集合

Map集合的遍历:普通for循环和增强for循环都不能遍历Map集合

// keySet():获取当前Map集合中所有键组成的Set集合
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
while(it.hasNext()){
String key = it.next();
Integer value = map.get(key);
System.out.println(key+"---"+value);
}
// entrySet():获取当前Map集合中所有映射关系组成的Set集合
Set<Map.Entry<String,Integer>> set = map.entrySet();
Iterator<Map.Entry<String,Integer>> it = set.iterator();
while(it.hasNext()){
Map.Entry<String,Integer> en = it.next();
System.out.println(en.getKey()+"---"+en.getValue());
}

Properties:Map的一个实现类,专门用于读写配置文件

Properties配置文件中的配置信息必须是要以键值对的形式出现的:

key=value
key:value
key value

注:每一行只能有一个键值对

Properties常用方法:

1.load(InputStream is/*字节输入流*/): 加载要读取的文件
/*示例:
InputStream is = new FileInputStream("src\test1.properties");//放文件的绝对地址或者相对地址
prop.load(is);
*/
2.load(Read r/*字符输入流*/):加载要读取的文件
3.getProperty(String k): 根据键获取值
4.getProperty(String k,String defVal): 根据键获取值,能获取到返回该值,否则返回默认值
5.setProperty(String k,String v): 将键值对临时写到集合中,不可能写到文件当中去