Java八股文——数据结构与集合(一)

集合整体框架

  • List集合:内部实现使用数组进行存储,扩容时会创建更大的数组空间,把原有数据复制到新数组中。
    • ArrayList支持快速随机访问,但是插入删除的速度较慢,因为可能需要移动元素。
    • LinkedList本质是双向链表,插入删除速度快,但是随机访问速度慢。实现了Deque接口,这个接口继承自Queue,同时具有栈和队列的性质,其中有指向第一个元素和最后一个元素的指针。
  • Queue集合:先进先出的数据结构。BlockingQueue具有先进先出和阻塞操作的特点,经常作为Buffer(数据缓冲区)使用。
  • Map集合:HashMap线程不安全,ConcurrentHashMap线程安全,TreeMap是Key有序的Map类集合。
  • Set集合:HashSet使用HashMap来实现,Value固定为一个静态对象。TreeSet使用TreeMap实现,保证插入后的集合是有序的。LinkedHashSet继承自HashSet,内部使用链表维护了元素的插入顺序。

集合初始化

ArrayList,默认的初始值大小是10,如果超过此次大小,那么会扩容,每次变为原来的1.5倍,并且把原有数据复制到新数组中。如果需要将1000个元素放入,但未指定初始大小,那么需要扩容13次,此时可能会有OOM问题。所以此时最好是new ArrayList(1000);

HashMap,默认的初始值大小是16,如果超过此次大小,那么会扩容,每次扩容增加2倍。不过不是在new的时候初始化的,而是在第一次调用put的时候分配的。Capacity决定容量大小(默认16),Load Factor决定填充比例(默认0.75),基于这两个的乘积,内部用threshold表示其中能放入的元素个数。

数组与集合

类型与中括号紧挨来定义数组,比如String[]用于指代String数组对象。

// 静态初始化
String[] args = {"a", "b"};
Object obj = args;
((String[]) obj)[0] = "object";

// 动态初始化
String[] args1 = new String[2];
args1[0] = "a";
args1[1] = "b";

函数式接口进行遍历:

Arrays.asList(args3).stream().forEach(x -> System.out.println(x));
Arrays.asList(args3).stream().forEach(System.out::println);

Arrays是针对数组对象进行操作的工具类,包括数组的排序、查找。对比和拷贝等操作。

数组转集合

public static void main(String[] args) {
    String[] stringArray = new String[3];
    stringArray[0] = "one";
    stringArray[1] = "two";
    stringArray[2] = "three";

    List<String> stringList = Arrays.asList(stringArray);
    // 此处方法可以将集合和数组中的值都进行修改
    stringList.set(0, "oneList");
    // 输出"oneList"
    System.out.println(stringList.get(0));
    // 输出"oneList"
    System.out.println(stringArray[0]);

    // 以下三行可以正常编译,但是运行时会报错
    // 是因为asList()返回的是一个继承自AbstractList但未实现add、remove、clear方法的内部类
    // 由于是继承,所以编译不会报错,但是AbstractList里的add、remove、clear方法会直接抛出异常
    // 那么此时未实现这三个方法的内部类在执行时,实际上执行的就是AbstractList里的add、remove、clear方法
    // 所以会抛出异常
    stringList.add("four");
    stringList.remove(0);
    stringList.clear();
}

注意:数组转集合的时候,不能使用其修改集合相关的方法,add/remove/clear会抛出异常,仅可使用set()

通过set()可以修改元素的值,但是原有数组对应位置的值同时也会被修改,但是不能进行修改元素个数的任何操作。

Arrays.asList()体现的是适配器模式,后台的数据仍是原有数组asList()返回的是一个Arrays的内部类,它没有实现集合个数的相关修改方法,所以会抛出异常。

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

ArrayList非彼ArrayList,它是Arrays的内部类,只提供了AbstractList里部分方法的实现,如下:

private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
    private final E[] a;

    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }
        
    @Override
    public E set(int index, E element) {
        E oldValue = a[index];
        a[index] = element;
        return oldValue;
    }
}

异常是由这个内部类的父类AbstractList抛出:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }
    // 次方法最终会调用remove(),从而也会有异常
    public void clear() {
        removeRange(0, size());
    }
}

所以,在使用数组转集合的时候,需要使用java.util.ArrayList直接创建一个新集合,此时对这个新集合做修改就不会影响到原来的数组了。

List<String> objectList = new java.util.ArrayList<>(Arrays.asList(stringArray));

集合转数组

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(3);
        list.add("one");
        list.add("two");
        list.add("three");

        // 不要使用无参方法把集合转换成数组,这样会导致泛型丢失
        Object[] array1 = list.toArray();

        // array2 数组长度小于集合元素个数
        // 结果为[null, null]
        String[] array2 = new String[2];
        list.toArray(array2);
        System.out.println(Arrays.asList(array2));
        // 结果为[one, two, three]
        String[] test = list.toArray(array2);
        System.out.println(Arrays.asList(test));

        // array3 数组长度等于集合元素个数
        // 结果为[one, two, three]
        String[] array3 = new String[3];
        list.toArray(array3);
        System.out.println(Arrays.asList(array3));

    }

toArray()源码如下:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

核心:如果传入数组的length大于等于集合的大小,那么会将集合中的数据复制到参数指定的数组中,并且返回;否则会创建一个新的数组,将集合元素复制到数组后,返回此数组

所以,如果集合是List<String>, 那么要将其转成数组,一般采用

String[] strArray = list.toArray(new String[0]);

当数组容量等于集合大小时,运行最快,消耗空间最小;如果设置不合理会造成性能和空间的浪费。

ArrayList中存储数据的是:

transient Object[] elementData;

集合序列化

transient表示此字段在序列化的时候会被忽略。在集合序列化时会调用writeObject写入流中,在网络客户端反序列化的writeObject时,会重新赋值到新对象的elementData中。

原因:因为elementData的容量经常会大于实际存储元素的数量,所以只需发送真正有实际值的数组元素即可。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据结构与算法八股文是一个常见的面试问题,主要考察对数据结构和算法的理解和应用能力。以下是一个典型的数据结构与算法八股文: 1. 数组 - 数组的特点和使用场景 - 常见的数组操作:增删改查、旋转、反转等 - 数组的时间复杂度和空间复杂度分析 2. 链表 - 链表的特点和种类:单链表、双链表、循环链表等 - 常见的链表操作:插入、删除、反转、合并等 - 链表的时间复杂度和空间复杂度分析 3. 栈和队列 - 栈和队列的特点和应用场景 - 栈和队列的实现方式:数组实现、链表实现等 - 常见的栈和队列操作:入栈、出栈、入队、出队等 4. 树和二叉树 - 树和二叉树的特点和应用场景 - 树和二叉树的遍历方式:前序、中序、后序、层次遍历等 - 常见的树和二叉树操作:插入、删除、查找等 5. 图 - 图的特点和应用场景 - 图的表示方式:邻接矩阵、邻接表等 - 常见的图算法:深度优先搜索、广度优先搜索、最短路径、最小生成树等 6. 排序算法 - 常见的排序算法:冒泡排序、插入排序、选择排序、快速排序、归并排序等 - 排序算法的时间复杂度和稳定性分析 - 如何选择合适的排序算法 7. 查找算法 - 常见的查找算法:顺序查找、二分查找、哈希查找等 - 查找算法的时间复杂度和空间复杂度分析 - 如何选择合适的查找算法 8. 动态规划 - 动态规划的基本思想和应用场景 - 动态规划问题的特征和求解方法 - 如何优化动态规划问题的时间复杂度

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值