集合体系面试题

集合和数组的区别

数组:用来存储相同数据类型的一个容器,数组一旦确定,长度不可变
集合:用来存放对象引用的一个容器
区别
数组:
1)数组的长度是不可变的,集合的长度是可变的
2)数组可以存基本数据类型和引用数据类型
集合: 只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类

集合体系结构

			单列集合的体系:
				Collection					单列集合的顶层接口
					List					有序集合子接口
						ArrayList	        底层数组:查询快、增删慢,线程不安全		
											
						LinkedList          底层链表:查询慢、增删快,线程不安全
                        
                        Vector				底层数组:查询快、增删慢,线程安全效率低
集合体系			  Set					  无序集合的子接口
						HashSet				底层hashmap,线程不安全
							LinkedHashSet   底层LinkedHashMap有序,线程不安全
			            TreeSet             底层红黑树,线程不安全
			双列集合的体系
				Map 						双列集合的顶层接口
					HashMap					底层:数组+链表+红黑树,线程不安全
						LinkedHashMap       底层:数组+链表+红黑树+双向链表,保持遍历顺序和插入顺序一致的问题,线程不安全
          			TreeMap                 底层:红黑树,线程不安全
          			HashTable               底层同HashMap,线程安全,在put方法前面加上了synchronizen关键字
              

集合遍历

1)单列集合的遍历

a.迭代器遍历

public class Demo1 {
    public static void main(String[] args) {
        Collection<Integer> c = new ArrayList<>();
        c.add(1);
        c.add(2);
        c.add(3);
        c.add(4);
        c.add(5);
        //获取迭代器对象
        Iterator<Integer> it = c.iterator();
        //while循环遍历取值
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
}

b.增强for循环(本质也是迭代器)

public class Demo2 {
    public static void main(String[] args) {
        Collection<Integer> c = new ArrayList<>();
        c.add(1);
        c.add(2);
        c.add(3);
        c.add(4);
        c.add(5);
        for (Integer integer : c) {
            System.out.println(integer);
        }
    }
}

c.List集合特有的遍历方式

public class Demo3 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

d.Set集合的遍历方式: 迭代器遍历, 增强for遍历

e.转为数组再遍历

public class Demo4 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Object[] array = list.toArray();
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }
}

2)Map集合的遍历方式

a.键遍历

获取Map集合中的所有键,放到一个Set集合中,遍历该Set集合,获取到每一个键,根据键再来获取对应的值。【根据键获取值】

案例

package com.tohka.demos;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo1 {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        map.put("大郎",1);
        map.put("宝强",2);
        map.put("羽凡",3);
        map.put("亦凡",4);
        map.put("乃亮",5);
        Set<String> set = map.keySet();
        for (String s : set) {
            System.out.println(s+"==="+map.get(s));
        }
    }
}

b.键值对遍历

获取Map<K,V>集合中,所有的键值对(Entry)对象,以Set集合形式返回。方法提示:entrySet()。

案例

package com.tohka.demos;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo2 {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<>();
        map.put("大郎",1);
        map.put("宝强",2);
        map.put("羽凡",3);
        map.put("亦凡",4);
        map.put("乃亮",5);
        Set<Map.Entry<String, Integer>> set = map.entrySet();
        for (Map.Entry<String, Integer> entry : set) {
            System.out.println(entry.getKey()+"=="+entry.getValue());
        }
    }
}

并发修异常的产生原因和解决办法

导致异常原因: 在迭代器遍历过程中使用集合的引用进行元素的添加和删除
解决方法: 
	1. 通过for循环遍历集合,在遍历过程中可以进行添加和删除元素
    2. 使用迭代器中的remove()方法
    3. 使用ListIterator<E> listIterator()返回列表中的列表迭代器(按适当的顺序)。
        -- add(E e) 添加元素
        -- remove() 移除元素

5.TreeSet的自然排序和比较器排序

自然排序:从小到大(1-n),从a到z

构造方法

TreeSet() : 根据元素的自然排序进行排序

TreeSet(Comparator c) : 根据指定的比较器进行排序

1)自然排序Comparable接口的使用

案例:Student类中有年龄和姓名两个属性, 将几个Student对象存储在集合中, 要求按照年龄从小到大进行排序, 年龄相同时, 按照姓名的字母顺序排序

思路 :

  1. Student类实现默认排序的Comparable接口(因为TreeSet空参数构造, 默认调用存储对象中重写的Comparable接口的compareTo方法作为自然排序的规则)

  2. Student类重写compareTo排序方法, 排序方法每次比较两个对象是否重复以及大小关系

a : 得出0结果, 证明两个对象重复, 后面的对象无法存储进集合中

b : 得出”正数”结果, 证明后面的对象比前面对象大, 因此需要存储的位置在后

c : 得出”负数”结果, 证明后面的对象比前面对象小, 因此需要存储的位置在前

就是根据compareTo比较对象的结果, 决定对象是否可以存储进入集合, 以及存储在集合中的位置

实体类

package com.tohka.entity;

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=‘" + name + ‘\‘‘ +
                ", age=" + age +
                ‘}‘;
    }

    @Override
    public int compareTo(Student o) {
        int num1 = this.age - o.age;
        int num2 = num1 == 0?this.name.compareTo(o.name):num1;
        return num2;
    }
}

测试代码

package com.tohka.test;

import com.tohka.entity.Student;

import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest1 {
    public static void main(String[] args) {
        Set<Student> set = new TreeSet<>();
        set.add(new Student("张三",12));
        set.add(new Student("李四",15));
        set.add(new Student("王五",10));
        set.add(new Student("赵六",18));// z
        set.add(new Student("田七",18));// t
        set.add(new Student("赵六",18));

        for (Student student : set) {
            System.out.println(student.getName()+"==="+student.getAge());
        }
    }
}

2)比较器排序

  1. 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的

  2. 比较器排序,就是让集合构造方法接收Comparator的实现类对象,

重写compare(T o1,T o2)方法

  1. 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

实体类

package com.tohka.entity;

public class Student  {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=‘" + name + ‘\‘‘ +
                ", age=" + age +
                ‘}‘;
    }
  
}

测试代码

package com.tohka.test;

import com.tohka.entity.Student;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest2 {
    public static void main(String[] args) {
        Set<Student> set = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int num1 = o1.getAge() - o2.getAge();
                int num2 = num1 == 0? o1.getName().compareTo(o2.getName()): num1;
                return num2;
            }
        });
        set.add(new Student("张三",12));
        set.add(new Student("李四",15));
        set.add(new Student("王五",10));
        set.add(new Student("赵六",18));// z
        set.add(new Student("田七",18));// t
        set.add(new Student("赵六",18));

        for (Student student : set) {
            System.out.println(student.getName()+"==="+student.getAge());
        }
    }
}

HashSet如何保证元素唯一

1.调用元素的hashCode()方法获取元素的哈希值
2.通过哈希值和数组长度获取元素的存储位置(底层是与运算,可以理解为取余)
3.如果该位置没有元素存在,元素唯一,将该元素存储在该位置
4.如果该位置有元素存在,遍历该位置链表的所有元素,和新存入的元素比较哈希值
5.哈希值都不相同,元素唯一,将该元素存储在该位置
6.哈希值相同,调用equals()方法比较内容是否相同
7.内容不相同,元素唯一,将该元素存储在该位置
8.内容相同,元素不唯一,不存储

HashSe如何实现自定义类型的去重

HashSet集合存储JDK定义好的数据类型 : String, Integer,Double...直接可以保证去重复, 全部重写过HashCode和equals, HashSet集合存储自定义引用数据类型 : Student, Person ,Animal...HashSet没有根据对象中的成员变量进行去重动作

重写hashCode()和equals()方法

HashSet底层是通过HashMap来实现的

HashSet中的add方法, 实际上就是的调用了HashMap中的put方法, 因此HashSet中元素唯一, 与HashMap中key值唯一的原理一致,

9.HashMap中key值唯一分析

HashMap存储的是jdk中提供的类型的键,就可以直接保证键的唯一性

HashMap中存储的键,是自定义类型,无法保证键的唯一性需要重写hashCode和equals方法

10.HashMap和HashSet的关系

HashSet是由HashMap实现出来的,HashSet就是HashMap的键的那一列

将HashMap中的值的那一列隐藏掉,就变成了HashSet

HashSet保证元素唯一原理, 就是使用了HashMap中Key值唯一的原理

Map 集合有哪些实现类,分别具有什么特征

HashMap 线程不安全的键值对集合 允许 null 值,key 和 value 都可以 
HashTable 线程安全的键值对集合 不允许 null 值,key 和 value 都不可以(不用) 
TreeMap 能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器 

解决集合线程不安全的问题

1)单列集合

如何使用的 List 集合来保证线程安全
第一种.使用 Vector,线程安全但是效率低
 List list = new Vector();

第二种.使用 Collections 中的方法 synchronizedList 将 ArrayList 转换为线程安全的 List
 List list = new ArrayList();
 list = Collections.synchronizedList(list);

第三种.使用 java.util.current 包下的 CopyOnWriteArrayList(推荐)
 CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();

第四种.在使用前使用synchronized关键字进行同步

2)双列集合

第一种.使用hashtable,线程安全但是效率低
 Map map = new Hashtable();
第二种.使用Collections工具类中讲不安全集合转为安全集合的方法    
 Map map = new HashMap();
 map = Collections.synchronizedMap(map);

第三种.使用 java.util.concurrent(并发包) 下的 ConcurrentHashMap
 ConcurrentHashMap map = new ConcurrentHashMap();

第四种.在使用前使用synchronized关键字进行同步

ArrayList 与 LinkedList 区别

ArrayList 使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计到位移操作,所以比较慢。 
LinkedList 使用双向链接方式存储数据,每个元素都记录前后元素的指针, 所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快,然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或 后几个元素速度比较快。
开发中什么时候到 ArrayList?,我们在做查询的时候把 查询出来的数据经常存到 arraylist 里.

Iterater 和 ListIterator 之间有什么区别?

1,我们可以使用 Iterator 来遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。 
2,Iterator 只可以向前遍历,而 LIstIterator 可以双向遍历。
3,ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个 元素、获取前面或后面元素的索引位置。

HashTable 和 ConcurrentHashMap 的区别

相同点:它们都可以用于多线程的环境, 
不同点:Hashtable 的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。 而ConcurrentHashMap 引入了分割(segmentation),不论它变得多么大,仅仅需要锁定 map 的某个 部分,而其它的线程不需要等到迭代完成才能访问 map。 简而言之,在迭代的过程中,ConcurrentHashMap 仅仅锁定 map 的某个部分,而 Hashtable 则会锁 定整个 map

arrayList初始化的时候创建多大的数组

arrayList底层维护了一个大小为10的 Object[],每次扩容是乘以1.5再加1

如果hashtable存储null值会怎么样?

编译通过,运行报空指针异常

介绍一下ConcurrentHashMap吧

该集合的特点:
1、底层是哈希表结构
2、此集合是线程安全的,但是某些功能不必锁定。比如get() 
   为了提高效率,不会将整个集合全部锁定。
3、不会抛出ConcurrentModificationException并发修改异常
   此集合支持遍历过程中添加,删除元素

TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?

1.TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。
2.TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。
3.Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象必须实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。

集合体系面试题

上一篇:leetcode 二叉树的中序遍历(递归与非递归) 简单


下一篇:ROS开发过程