java中的容器

20 阅读8分钟

容器

什么是容器

  • 用来存放一串数据的东西可以被称为容器,java中除了数组,还有其他对象类型的容器。我们可以根据应用场景来选择合适的容器。

ArrayList

相对于数组的固定长度来说,ArrayList可以根据数据的数量,动态扩展自身长度。理论上来说,只要计算机内存够,可以无限扩展

//定义一个ArrayList
ArrayList<String> student = new ArrayList<String>();
//添加一个数据 
student.add("小李");
//往指定位置添加一个数据,0指的是需要添加位置的下标
//这个方式会让原有数据往后移,为这条新的数据腾出位置 
student.add(0, "老张");

//获取指定位置的数据,如果超出容器本身的长度,会报数组下标越界异常 
student.get(2)

//删除指定位置的数据,如果删除成功会返回这条数据 
String string = student.remove(5);
  • ArrayList内的元素有固定顺序,可以直接遍历/迭代
//使用普通for循环遍历
for (int i = 0; i < student.size(); i++) {  
    System.out.println("第" + (i + 1) + "位是" + student.get(i));  
}
//使用foreach进行迭代
for(String str : student){  
    System.out.println(str);  
}//意思是说将student对象中的内容一个一个的拿出给String变量str保存
  • 同时对于ArrayList这种动态扩缩容的容器,有一个错误是经常犯的,就是使用fori的形式进行循环的同时进行判断并移除元素,如下方代码所示
import java.util.ArrayList;
public class ArrayListTest4 {
     // 需求:创建一个存储String的集合,内部存储(test,张三,李四,test,test)字符串
    //   删除所有的test字符串,删除后,将集合剩余元素打印在控制台
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("test");
        list.add("张三");
        list.add("李四");
        list.add("test");
        list.add("test");

        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            if("test".equals(s)){
                list.remove(i);
            }
        }

        System.out.println(list);//[张三, 李四, test]
        // 可以发现有一个test字符串是没有移除成功
    }
}
  • 是为啥呢,我们可使用debug看下,如下图所示,可看出,因为ArrayList是动态的集合(容器),会因为移除元素容量发生变化,同时元素的下标也会发生变换,通过fori加if判断的方式移除元素会造成元素的下标错位 image.png
  • 可以使用再移除元素后添加i--进行修正,但更加建议改为foreach的方循环,或使用Iterator将ArrayList转换为迭代器,再使用while(iterator.hasnext())进行迭代,更可以直接使用集合中的removeIf() api,如下方代码所示:
    • 使用i--抵消删除元素导致的错位
      public static void main(String[] args) {
          ArrayList<String> list = new ArrayList<>();
          list.add("test");
          list.add("张三");
          list.add("李四");
          list.add("test");
          list.add("test");
      
          for (int i = 0; i < list.size(); i++) {
              String s = list.get(i);
              if("test".equals(s)){
                  list.remove(i);
                  i--;
              }
          }
          System.out.println(list);
      }
      
    • 使用从后往前遍历,这样就不会下标越界了
      //    从后往前删除,就不会出现下标错位了
         public static void main(String[] args) {
             ArrayList<String> list = new ArrayList<>();
             list.add("test");
             list.add("张三");
             list.add("李四");
             list.add("test");
             list.add("test");
      
             for (int i = list.size() - 1; i >= 0; i--) {
                 String s = list.get(i);
                 if("test".equals(s)){
                     list.remove(i);
                 }
             }
             System.out.println(list);
         }
      
    • 使用迭代器对象iterator结合foreach进行遍历
      public static void main(String[] args) {
          ArrayList<String> list = new ArrayList<>();
          list.add("test");
          list.add("张三");
          list.ad("李四");
          list.add("test");
          list.add("test");
      
          Iterator<String> listIterator = list.iterator();
          while (listIterator.hasNext()) {
              String s = listIterator.next();
              if("test".equals(s)){
                  listIterator.remove();
              }
          }
      
          System.out.println(list);
      }
      
    • 使用AarrayList类中的removeIf方法
      public static void main(String[] args) {
          ArrayList<String> list = new ArrayList<>();
          list.add("test");
          list.add("张三");
          list.add("李四");
          list.add("test");
          list.add("test");
      
          list.removeIf("test"::equals);
          System.out.println(list);
      }
      
  • 集合存储对象内存图,实际上集合存储的对象是存放对象的地址,如下图所示

image.png

HashSet

  • HashSet中不允许存在两个相同的元素,新插入的数据如果已存在会替换之前的数据;
  • HashSet没有下标索引这一说法,因为它对位置没有概念,只知道自己的内部存放了哪些数据。
  • 具体使用方法参考文档- 具体使用方法参考文档HashSet (Java SE 17 & JDK 17) --- HashSet (Java SE 17 & JDK 17))
/定义一个HashSet  
HashSet set = new HashSet();

//往里面存数据  
set.add("老孙");

//将HashSet转为数组,再通过数组的形式处理里面的元素  
String[] strs = set.toArray(new String[0]);
// toArray(new Object[])可以将集合对象转化为对应类型的数组,其中Object参数为指定的数组类型,
//当类型不匹配时,会抛出ArrayStoreException异常  


for (int i = 0; i < strs.length; i++) {  
System.out.println(strs[i]);  
}

//获取HashSet的迭代器,使用迭代器对象来循环遍历处理里面的元素  
Iterator iterator = set.iterator();  
//hashNext方法会返回一个布尔值,用来判断是否还有下一个元素可以被遍历,如果有则返回true。如果已经到了末尾,则返回false  
while (iterator.hasNext()) {  
//使用next获取下一个元素  
String next = iterator.next();  
System.out.println(next);  
}

//也可以使用for-each,for-each会将不可迭代的对象set,隐式转换为可迭代对象,和Iterator类中的iterator方法类似  
for(String str : set){  
System.out.println(str);  
}

//这样写式不对的,因为for-each本来就会隐式的使用iterator()方法,所以Foreach 不适用于类型 'java.util.Iterator<java.lang.String>'  
/*  
Iterator iterator = set.iterator();  
for(String str : iterator){  
System.out.println(str);  
}* /

HashMap

  • 和HashSet类似,HashMap只允许存在1个唯一的Key。新添加的Key如果已存在,会把旧的元素替换。
  • 在HashMap中作为key的数据最好要是唯一的,即为最好要为主键,如果两个key相同,后写入的key对应的数据会将之前写入的数据覆盖,即只保留最新一条数据
//定义一个HashMap //K:key查询的关键值 V:value具体的数据  
HashMap<String, Student> hashMap = new HashMap<>();  
//往里面添加一条数据 第一个参数需要指定key值,第二个参数指定数据  
hashMap.put(laosun.number, laosun);  
//通过key查询数据,返回对应的数据类型  
Student query = hashMap.get(laosun.number);

//获取hashmap的长度  
int length = hashMap.size();

//获取hashmap中所有key的一个试图  
Set keySet = hashMap.keySet();

//通过key的集合遍历,使用for-each进行遍历
hashmap for (String key : keySet) {  
Student stu = hashMap.get(key);  
}

Set视图

  • 在 Java 集合框架中,一个“视图”是指一种特殊的集合,它是原始集合的一个动态映射
  • 例如前面的keySet会返回一个Set视图
  • 视图特点:
    • 动态关联: 例如keySet()返回的Set试图与其HashMap本身是紧密关联的,当对应的HashMap发生改变,keySet()返回的Set试图也会同时发生改变
    • 只读:试图不像集合,是不能像集合类型一样进行修改的
    • 通过试图删除Map中的内容:我们可以使用Set中的remove方法实现删除Map中的键值对(注意这里实际上是删除的HashMap中的键值对,不是修改的试图,只是通过试图的键来删除HashMap中的键值对)
    //创建一个 HashMap 实例  
    Map<String, Integer> hashMap = new HashMap<>();  
    hashMap.put("one", 1);  
    hashMap.put("two", 2);  
    hashMap.put("three", 3);  
    Set keys = hashMap.keySet();  
    // 从 keys Set 中移除一个键  
    keys.remove("two");
    System.out.println(hashMap);//{one=1, three=3}
    System.out.println(keys);//[one, three]
    
    • 元素顺序keySet() 返回的 Set 视图的顺序取决于底层 Map 的实现。例如:
      • HashMap 默认不保证元素的顺序。
      • LinkedHashMap 会保持元素的插入顺序。
      • TreeMap 会按照键的自然顺序或自定义的比较器顺序来排序。

示例

  • 使用几种容器对Student类的对象进行管理
  • Student类:
import java.util.HashMap;
import java.util.Scanner;

public class Student {
    int number;
    int age;
    String name;
    int gender;
    public Student(int number, int age, String name, int gender) {
        this.number = number;
        this.age = age;
        this.name = name;
        this.gender = gender;
    }

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

    public Student() {

    }

    @Override
    public String toString() {
        return "Student{" +
                "number=" + number +
                ", age=" + age +
                ", name='" + name + ''' +
                ", gender=" + gender +
                '}';
    }
    public static void main(String[] args) {
        Student laosun = new Student(10, 18, "老孙", 1);
        Student laowang = new Student(11, 19, "老王", 1);
        Student laowu = new Student(12, 20, "老吴", 1);
        Student laoli = new Student(13, 21, "老李", 0);
        System.out.println("输入一个学号");
        Scanner scanner = new Scanner(System.in);
        int i = scanner.nextInt();

            ...
    }
  • 使用ArrayList
public static void main(String[] args) {
    Student laosun = new Student(10, 18, "老孙", 1);
    Student laowang = new Student(11, 19, "老王", 1);
    Student laowu = new Student(12, 20, "老吴", 1);
    Student laoli = new Student(13, 21, "老李", 0);

    System.out.println("输入一个学号");
    Scanner scanner = new Scanner(System.in);
    int i = scanner.nextInt();

    System.out.println("====================================");

    // 我们可以尝试使用ArrayList来保存信息,通过循环遍历查询数据
    ArrayList<Student> students = new ArrayList<>();
    students.add(laosun);
    students.add(laowang);
    students.add(laowu);
    students.add(laoli);
    for (Student Str: students) {
        if (Str.number == i){
            System.out.println("你查询到的学生信息是:" + Str);
            break;
        }
     }
}
  • 使用HashSet
public static void main(String[] args) {
    Student laosun = new Student(10, 18, "老孙", 1);
    Student laowang = new Student(11, 19, "老王", 1);
    Student laowu = new Student(12, 20, "老吴", 1);
    Student laoli = new Student(13, 21, "老李", 0);

    System.out.println("输入一个学号");
    Scanner scanner = new Scanner(System.in);
    int i = scanner.nextInt();
    System.out.println("====================================");
    
    //也可以试着用HashSet来保存,并转化为一个可迭代对象进行循环遍历查询
    HashSet<Student> students = new HashSet<>();
    students.add(laosun);
    students.add(laowang);
    students.add(laowu);
    students.add(laoli);
    // for-each包含隐式转化可跌代对象,不用使用Iterator类中的iterator方法
    for(Student Str: students){
        if (Str.number == i){
            System.out.println("你查询到的学生信息是:" + Str);
                break;
        }
    }
    Iterator<Student> iterator = students.iterator();
    while (iterator.hasNext()){
        Student Str = iterator.next();
        if (Str.number == i){
            System.out.println("你查询到的学生信息是:" + Str);
            break;
        }
    }
}
  • 使用HashMap
    • k:key v:value 键值对 我们通过键key来查询值value
    • HashMap 要求我们填入基本类型的对象类型(包装类型) 如:int-- interger,char-- Character
public static void main(String[] args) {
    Student laosun = new Student(10, 18, "老孙", 1);
    Student laowang = new Student(11, 19, "老王", 1);
    Student laowu = new Student(12, 20, "老吴", 1);
    Student laoli = new Student(13, 21, "老李", 0);
    System.out.println("输入一个学号");
    Scanner scanner = new Scanner(System.in);
    int i = scanner.nextInt();
    System.out.println("====================================");
    HashMap<Integer, Student> studentMap =  new HashMap<>();
    studentMap.put(laosun.number,laosun);
    studentMap.put(laowang.number,laowang);
    studentMap.put(laowu.number,laowu);
    studentMap.put(laoli.number,laoli);

    Student query = studentMap.get(i);
    System.out.println("你查询到的学生信息是:" + query);

    System.out.println("总人数:" + studentMap.size());

    System.out.println("HashMap中key的集合:" + studentMap.keySet());

    //在HashMap中作为key的数据最好要是唯一的,即为最好要为主键,如果两个key相同,后写入的key对应的数据会将之前写入的数据覆盖,即只保留最新一条数据
    System.out.println("==================学生列表(乱序)==================");
    for (int key : studentMap.keySet()) {
        Student student = studentMap.get(key);
        System.out.println(student);
    }
}