Java 学习笔记10 集合

Java集合简介

  1. 数组存在的限制:
  • 数值初始化后大小不可变;
  • 数组只能按索引顺序存取;
  1. Collection:包含在Java标准库自带的java.util中,是除了Map外其他所有集合类的根接口;
  2. java.util包所提供的集合的三种主要类型:
  • List:一种有序列表的集合;
  • Set:一种无重复元素的集合;
  • Map:一种通过键值(key-value)对查找的映射表集合;
  1. 集合的设计特点
  • 实现了接口和实现类相分离;
  • 支持泛型,可以限制在一个集合中只能放入同一种数据类型的元素;
  • 集合总是通过统一方式——**迭代器(Iterator)**实现,其最明显的好处在于无需知道集合内部元素是按何种方式存储;
  1. 遗留的集合类
  • Hashtable:一种线程安全的Map实现;
  • Vector:一种线程安全的List实现;
  • Stack:基于Vector实现的FIFO的栈;
  • Enumeration<E>:已被Iterator<E>取代;

List

  1. 定义:一种有序链表,其内部按放入元素的先后顺序存放,每个元素可通过索引确定自己的位置,索引同数组均从0开始;

  2. List<E>接口主要用到的接口方法:

方法 功能
void add(E e) 末尾添加元素
void add(int index, E e) 指定索引添加元素
int remove(int index) 删除指定索引元素
int remove(Object e) 删除某元素
E get(int index) 获取指定索引元素
int size() 获取链表大小(即所含元素个数)
boolean contains(Object obj) 判断List是否包含某个指定元素
int indexOf(Object obj) 返回某个元素的索引,若不存在则返回-1
  1. ArrayListLinkedList的区别
ArrayList LinkedList
获取指定元素 速度很快 需要从头开始查找元素
添加元素到末尾 速度很快 速度很快
在指定位置添加/删除 需要移动数据 无需移动元素
内存占用 较大
  1. List常见操作
  • 创建

      // 1. ArrayList
      List<T> list = new ArrayList<T>();
      // 2. LinkedList
      List<T> list = new LinkedList<T>();
      // 3. of(),不接受null
      List<T> list = List.of(T t1, T t2, ...);
    
  • 遍历

      import java.util.List;
    
      public class Main{
          public static void main(String[] args) throws Exception{
              List<String> list = List.of("apple", "pear", "banana");
              for(int i = 0; i < list.size(); i++){
                  String s = list.get(i);
                  System.out.println(s);
              }
          }
      }
    
      import java.util.Iterator;
      import java.util.List;
    
      public class Main{
          public static void main(String[] args) throws Exception{
              List<String> list = List.of("apple", "pear", "banana");
              for(Iterator<String> it = list.iterator(); it.hasNext();){
                  String s = it.next();
                  System.out.println(s);
              }
          }
      }
    
      import java.util.List;
    
      public class Main{
          public static void main(String[] args) throws Exception{
              List<String> list = List.of("apple", "pear", "banana");
              for(String s:list){
                  System.out.println(s);
              }
          }
      }
    
  1. Iterator对象的两个方法
方法 功能
boolean hasNext() 判断是否有下一个元素
E next() 返回下一个元素
  1. List转换Array三种方法:
  • 调用toArray()方法直接返回一个Object[]数组,但是会导致丢失类型信息;

      import java.util.List
    
      public class Main{
          public static void main(String[] args) throws Exception{
              List<String> list = List.of("Apple", "Huawei", "Xiaomi");
              Object[] array = list.toArray();
              for(Object obj : array){
                  System.out.println(obj)
              }
          }
      }
    
  • toArray(T[])传入一个类型相同的ArrayList内部自动将元素复制到传入的Array中;

      import java.util.List;
      public class Main{
          public static void main(String[] args) throws Exception{
              List<Integer> list = List.of(12, 34, 56, 78);
              Integer[] array = list.toArray(new Integer[4]);
              for(Integer n; array){
                  System.out.println(n);
              }
          }
      }
    
  • 通过List接口定义的T[] toArray(IntFunction<T[]> generator)方法;

      Integer[] array = list.toArray(Integer[]::new);
    
  1. Array转换为List的方法
  • 利用List.of(T...)

      Integer[] array = {1, 3, 4};
      List<Integer> list = List.of(array);
    

equals方法

  1. equals()方法所要满足的条件:
  • 自反性(Reflexive):对非nullx而言,x.equals(x)必须为true
  • 对称性(Symmetric):对非nullxy而言,x.eauals(y)y.equals(x)返回的结果相同;
  • 传递性(Transitive):对非nullxyz而言,若x.equals(y)truey.equals(z)也为true,则x.equals(z)也必须为true
  • 一致性(Consistent):对非nullxy而言,只要xy状态不变,则x.equals(y)总是一致返回truefalse
  • null间的比较:x.equals(null)永远返回false
  1. 编写equals()的步骤:
  • 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
  • instanceof判断传入的待比较的Object是否为当前类型,若是则继续比较,否则返回false
  • 对引用类型用Objects.equals()比较,对基本类型直接用==比较;
  1. 注意: 使用Objects.equals()比较两个引用类型是否相等的目的是省去了判断null的麻烦,两个引用类型均为null时他们也相等;

Map

  1. 通过一个键去查询对应值,用List实现效率极低,平均需要扫描一半元素才能确定,使用Map这种键值(key-value)映射表,能高效通过key查找value

  2. 常用方法

方法 功能
boolean containsKey(K key) 查询某个key是否存在
void put(K key, V value) keyvalue做映射并放入Map
V get(K key) 通过key获取对应value,若key不存在则返回null
  1. 注意: Map中不存在重复的key,因为将不同value放入相同的key,只会讲原有key-value映射对中的value替换掉;但是不同key可以对应相同value

  2. 常见操作

  • 创建

      import java.util.Map;
      import java.util.HashMap;
    
      public class Main{
          public void main(String[] args) throws Exception{
              Map<String,Integer> map = new HashMap<>();
              map.put("Apple", "10000");
              map.put("Huawei", "5000");
              System.out.println(map.get("Apple"));
          }
      }
    
  • 遍历

      // 只遍历key
      import java.util.Map;
      import java.util.HashMap;
    
      public class Main{
          public void main(String[] args) throws Exception{
              Map<String,Integer> map = new HashMap<>();
              map.put("Apple", "10000");
              map.put("Huawei", "5000");
    
              for(String key : map.keySet()){
                  Integer value = map.get(key);
                  System.out.println(key + ":" + value);
              }
          }
      }
    
      // 同时遍历key和value
    
      import java.util.Map;
      import java.util.HashMap;
    
      public class Main{
          public void main(String[] args) throws Exception{
              Map<String,Integer> map = new HashMap<>();
              map.put("Apple", "10000");
              map.put("Huawei", "5000");
    
              for(Map.Entry<String, Integer> entry : map.entrySet()){
                  String key = entry.getKey();
                  Integer value = map.getValue();
                  System.out.println(key + ":" + value);
              }
          }
      }
    
  1. 注意: 遍历Map时,输出的key是无序的;

equals和hashCode

  1. hashCode()方法需要遵循的规范:
  • 若两个对象相等,则两个对象的hashCode()必须相等;
  • 若两个对象不相等,则两个对象的hashCode()尽量不相等;
  1. 正确使用Map的原则:
  • 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true
  • 作为key的对象还必须覆写hashCode()方法;
  1. 编写equals()hashCode()方法必须遵循的原则:
  • equals()用于比较的每个字段都必须在hashCode()中用于计算;
  • equals()中未使用的字符,hashCode()中绝对不能出现;

EnumMap

  1. 相关定义

针对enum类型,可使用EnumMap,它在内部以一个非常紧凑的数组存储value,且根据enum类型的key直接定位到内部数组的索引,不需要计算hashCode(),效率高且没有额外浪费空间;

 import java.time.DayOfWeek;
 import java.util.*;

 public class Main {
     public static void main(String[] args) throws Exception{
         Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
         map.put(DayOfWeek.MONDAY, "星期一");
         map.put(DayOfWeek.TUESDAY, "星期二");
         map.put(DayOfWeek.WEDNESDAY, "星期三");
         map.put(DayOfWeek.THURSDAY, "星期四");
         map.put(DayOfWeek.FRIDAY, "星期五");
         map.put(DayOfWeek.SATURDAY, "星期六");
         map.put(DayOfWeek.SUNDAY, "星期日");
         System.out.println(map);
         System.out.println(map.get(DayOfWeek.SUNDAY));
     }
 }

TreeMap

  1. Map继承树

  1. HashMap是一种空间换时间的映射表,内部key无序,而SortedMap在内部会对key进行排序,作为接口其实现类是TreeMap
 import java.util.*;

 public class Main{
     public static void main(String[] args) throws Exception{
         Map<String, Integer> map = new TreeMap<>();
         map.put("Apple", "10000");
         map.put("Xiaomi", "3000");
         map.put("Huawei", "5000");

         for(String key : map.keySet()){
             System.out.print(key); // Apple, Huawei, Xiaomi
         }
     }
 }
  1. 作为SortedMapkey必须实现Comparable接口,或者传入Comparator,同时严格按照compare()规范实心比较逻辑;

Properties

  1. 读取配置文件的步骤:
  • 创建Properties实例;

  • 调用load()读取文件;

  • 调用getProperty()获取配置,若key不存在,则返回null

      String f = "setting.properties";
      // 创建实例
      Properties props = new Properties();
      // 读取文件
      props.load(new java.io.FileInputStream(f));
      // 获取配置
      String filePath = props.getProperty("属性名");
    
  1. 写入配置文件
 Properties props = new Properties();
 props.setProperty("url", "https://cunyu1943.github.io");
 props.setProperty("author", "村雨遥");
 props.store(new FileOutputStream("./home/cunyu/setting.properties"), "写入的properties注释")

Set

  1. 定义:用于存储不重复的元素集合;
  • 放入HashSet的元素与作为HashMapkey要求相同;
  • 放入TreeSet的元素与作为TreeMapkey要求相同;
  1. 常用方法:
方法 功能
boolean add(E e) 将元素添加入Set<E>
boolean remove(Object e) 将元素从Set<E>删除
boolean contains(Object e) 判断是否包含某元素
int size() Set<E>中的元素个数
  1. Set继承树

Set接口不保证有序,而SortedSet保证元素有序;

  • HashSet无序,只实现了Set接口,未实现SortedSet接口;
  • TreeSet有序,因为实现了SortedSet接口;

Queue

  1. 定义:一个先进先出(FIFO:First In First Out)的有序表,与List的区别在于List可在任意位置添加/删除元素,而Queue只有两个操作;
  • 将元素添加到队列末尾;
  • 从队列头部取出元素;
  1. 常用方法
方法 功能
int size() 获取队列长度
boolean add(E e)/boolean offer(E e) 添加元素到队尾
E remove()/E poll() 获取队首元素并从队列中删除
E element()/E peek() 获取队首元素但不从队列中删除
  1. 获取或删除元素失败时不同方法的应对策略
功能 throw Exception 返回falsenull
添加元素到队尾 add(E e) boolean offer(E e)
取队首元素并删除 E remove() E poll()
取队首元素但不删除 E element() E peek()
  1. 注意: 避免将null加入队列,否则poll()方法返回null时,难以确定是取到了null元素还是队列为空;

PriorityQueue

  1. QueuePriortyQueue的区别:
  • PriorityQueue的出队顺序与元素的优先级有关,无论调用remove()poll()方法,返回的总是优先级最高的元素;
  • 使用PriorityQueue需要给每个元素定义“优先级”;
  1. PriortyQueue默认按元素比较的顺序排序(需实现Comparable接口),也可以通过Comparator自定义排序算法(元素不必实现Comparable接口);

  2. 举例

 import java.util.Comparator;
 import java.util.PriorityQueue;
 import java.util.Queue;

 public class Main {
     public static void main(String[] args) {
         Queue<User> queue = new PriorityQueue<>(new UserComparator());
         // 添加3个元素到队列:
         queue.offer(new User("Tim", "A1"));
         queue.offer(new User("Park", "A2"));
         queue.offer(new User("Manu", "V1"));
         System.out.println(queue.poll()); // Manu/V1
         System.out.println(queue.poll()); // Tim/A1
         System.out.println(queue.poll()); // Park/A2
         System.out.println(queue.poll()); // null,因为队列为空
     }
 }

 class UserComparator implements Comparator<User> {
     public int compare(User u1, User u2) {
         if (u1.number.charAt(0) == u2.number.charAt(0)) {
             // 如果两人的号都是A开头或者都是V开头,比较号的大小:
             return u1.number.compareTo(u2.number);
         }
         if (u1.number.charAt(0) == 'V') {
             // u1的号码是V开头,优先级高:
             return -1;
         } else {
             return 1;
         }
     }
 }

 class User {
     public final String name;
     public final String number;

     public User(String name, String number) {
         this.name = name;
         this.number = number;
     }

     public String toString() {
         return name + "/" + number;
     }
 }

Deque

  1. 定义:允许两头都进出的队列叫做双端队列(Double Ended Queue),即Deque

  2. Deque是一个接口,其实现类有ArrayDequeLinkedList

  3. QueueDeque出入队方法比较:

Queue Deque
添加元素到队尾 add(E e)/offer(E e) addLast(E e)/offerLast(E e)
取队首元素并删除 E remove()/E poll() E removeFirst()/E pollFirst()
取队首元素但不删除 E element()/E peek() E getFirst()/E peekFirst()
添加元素到队首 addFirst(E e)/offerFirst(E e)
取队尾元素并删除 E removeLast()/E pollLast()
取队尾元素但不删除 E getLast()/E peekLast()
  1. 举例
import java.util.Deque;
import java.util.LinkedList;

public class Main{
   public static void main(String[] args){
       Deque<String> deque = new LinkedList<>();
       deque.offerLast("A"); // A
       deque.offerLast("B"); // B->A
       deque.offerFirst("C"); // B->A->C
       System.out.println(deque.pollFirst()); // C, B -> A
       System.out.println(deque.pollLast()); // B, A
       System.out.println(deque.pollFirst()); // A
       System.out.println(deque.pollFirst()); // null
   }
}
  1. 抽象编程的一个原则:尽量持有接口,而非具体实现类;

Stack

  1. 定义:栈,一种后进先出(LIFO:Last In First Out)的数据结构,只能不断往stack中压入(push)元素,最后进去的元素最先弹出(pop);

  2. 出入栈的操作

方法 功能
push (E) 将元素压栈
pop (E) 将栈顶元素“弹出”
peek (E) 取栈顶元素但不弹出
  1. 利用Deque实现Stack功能
方法 功能
push (E)/addFirst(E) 将元素压栈
pop(E)/removeFirst() 将栈顶元素“弹出”
peek(E)/peekFirst(E) 取栈顶元素但不弹出
  1. Stack的作用
  • JVM在处理Java方法调用时会通过栈来维护方法调用的层次;

  • 利用栈对整数进行进制转换;

      import java.util.Deque;
      import java.util.LinkedList;
      // 十进制转十六进制
      public class Main{
          public static void main(String[] args){
              int intNum = 12500;
              Main demo = new Main();
              String hex = demo.toHex(intNum);
              if(hex.equalsIgnoreCase("30D4")){
                  System.out.println("Success");
              }else{
                  System.out.println("Failed");
              }
          }
    
          public String toHex(int n){
              Deque<Character> stack = new LinkedList<>();
              String alpha = "0123456789ABCDE";
              int remainder = 0;
              while(n>0){
                  remainder = n % 16;
                  if(remainder < 16){
                      stack.push(alpha.charAt(remainder));
                  }
                  n /= 16;
              }
    
              String result = "";
              while(stack.isEmpty() != true){
                  result += stack.pop();
              }
              return result;
          }
      }
    

Iterator

  1. 迭代器:通过Iterator对象遍历集合的模式,使用迭代器的好处在于调用方总是以统一方式遍历各种集合类型,而无需关系其内部存储结构;

  2. 当要在一个编写的集合类中使用for each循环时,需要满足以下条件:

  • 集合类实现Iterable接口,且接口要求返回一个Iterator对象;
  • Iterator对象迭代集合内部数据;
  1. 示例
 // Iterator
 import java.util.*;

 public class Main{
     public static void main(String[] args) throws Exception{
         ReverseList<String> rlist = new ReverseList<>();
         rlist.add("Apple");
         rlist.add("Orange");
         rlist.add("Pear");
         for(String s : rlist){
             System.out.println(s);
         }
     }
 }

 class ReverseList<T> implements Iterable<T>{
     private List<T> list = new ArrayList<>();

     public void add(T t){
         list.add(t);
     }

     @override
     public Iterator<T> iterator(){
         return new ReverseIterator(list.size());
     }

     class ReverseIterator implements Iterators<T>{
         int index;

         ReverseIterator(int index){
             this.index = index;
         }

         @override
         public boolean hasNext(){
             return index > 0;
         }

         @override
         public T next(){
             index--;
             return ReverseList.this.list.get(index);
         }
     }
 }
  1. Iterator是一种抽象数据访问模型,使用它的好处有如下几点:
  • 对任何集合均采用同一种访问模式;
  • 调用者对集合内部结构一无所知;
  • 集合类返回的Iterator对象知道如何迭代;
  1. Java提供标准迭代器模型,即集合类实现java.util.Iterable接口,返回java.util.Iterator实例;

Collections

  1. 创建空集合
方法 功能
List<T> emptyList() 创建空List
Map<K,V> emptyMap() 创建空Map
Set<T> emptySet() 创建空Set
of() 创建对应空集合
  1. 创建单元素集合
方法 功能
List<T> singletonList(T o) 创建单元素的List
Map<K,V> singletonMap(K key, V value) 创建单元素的Map
Set<T> singleton(T o) 创建单元素的Set
of(T o) 创建对应单元素集合
  1. 排序
 // 排序会改变List元素位置,因此传入的List必须可变

 import java.util.*;
 public class Main{
     public static void main(String[] args) throws Exception{
         List<String> list = new ArrayList<>();
         list.add("Huawei");
         list.add("Vivo");
         list.add("OnePlus");
         // 排序前
         System.out.println(list);
         Collection.sort(list);
         // 排序后
         System.out.println(list);
     }
 }
  1. 洗牌:将传入的有序List,随机打乱List内部元素顺序;
 import java.util.*;

 public class Main{
     public static void main(String[] args) throws Exception{
         List<Integer> list = new ArrayList<>();
         for(int i = 0; i < 10; i++){
             list.add(i);
         }

         // 洗牌前
         System.out.println(list);
         Collections.shuffle(list);
         // 洗牌后
         System.out.prinln(list);
     }
 }
  1. 不可变集合,封装时是通过创建一个代理对象,拦截所有修改方案,此时对原始可变List进行修改仍然可以,但是会对封装后的不可变List造成影响;
方法 功能
List<T> unmodifiableList(List<? extends T> list) 封装为不可变List
Set<T> unmodifiableSet(Set<? extends T> set) 封装为不可变Set
Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> map) 封装为不可变Map
  1. 线程安全集合
方法 功能
List<T> synchronizedList(List<T> list) 变为线程安全的List
Set<T> synchronizedSet(Set<T> set) 变为线程安全的Set
Map<K, V> synchronizedMap(Map<K, V> map) 变为线程安全的Map
发布了138 篇原创文章 · 获赞 72 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/github_39655029/article/details/105448442
今日推荐