Java基础篇--集合(collection)

在之前的文章中,已经发布了常见的面试题,这里我花了点时间整理了一下对应的解答,由于个人能力有限,不一定完全到位,如果有解答的不合理的地方还请指点,在此谢过。

本文主要描述的是java面试中几乎必问的集合内容,java的集合是jdk里面重要的内容,也是我们平时开发过程中最常用到的,所以无论是否为了准备面试,我们都要掌握好集合相关的知识。既然是重点,那就意味着集合类在java面试中的题目会非常多,尤其是hashmap,由于其设计精美,细节优化突出,常常是面试中集合的首选,所以我会把hashmap单独使用一篇文章来记录。

 

能简单说明一下你了解的集合么?

Java的集合分为两大类,collection和map。Collection是一组数据集合类型,可以理解为动态数组。其下包含list和set两个大类,List和set的最大区别是是否可以有重复元素,list是一组有序的可重复的数组,set是一组无序的不可重复的数组。Map是存入键值对,是一种key-value的形式。Map底下有两个大的实现类treemap和hashmap。具体看下图为集合类中常用的集合图,在介绍的时候,我们一般介绍常用的就行。

说说ArrayList、linkedlist、vector的区别?

其实三者都是collection接口下面的,都是动态数组。具体的差异点如下表:

 

ArrayList

Linkedlist

Vector

底层实现

数组

链表

数组

线程安全

不安全

不安全

安全

使用频次

最高

较高

不使用

扩容

增加0.5倍

链表,没有扩容概念

增加1倍

Vector是早期jdk实现的线程安全的集合,其实现基本是在ArrayList的方法上加上了锁同步,导致性能下降,现在基本已经使用concurrent系列替换了。ArrayList和linkedlist的核心区别在于底层实现,ArrayList基于数组的实现存在扩容机制,同时数组可以随机访问0(1),如果是尾部添加元素的话也是o(1)。如果是中间某个位置添加元素的话,arraylist调用的是系统拷贝函数(System.arraycopy),会把后面的内容一次性全部往后挪一位,所以时间复杂度也是o(1),当然了删除也类似,这样添加和删除也基本实现了o(1),而linkedlist的优势在于随机位置的删除和添加,但是它在找到哪个位置的时候,是线性的,所以基本上linkedlist在访问和删除上已经没有什么优势了,扩容可能是它的唯一优势。没有什么特殊情况,我们一般都是使用arraylist,也很少使用linkedlist和vector。

 

说说hashset和treeset的区别?

其实这两者底层是hashmap和treemap的延伸,所以两种在区别上和hashmap和treemap一致。Hashset是以hash表为主要的存储结构,treeset底层是以红黑树为主要结构存储的。Hashset是以key的hashcode值和equals方法来确定顺序的,而treeset是通过Comparable(外部比较器)和Comparator(内部比较器)来指定顺序的。

 

如何在循环中安全的删除一个ArrayList的数据?

这是一个基本的操作了,直接给出正确的删除方法哈,如下:

public static void main(String[] args) throws Exception {

        List<String> list = new ArrayList<>();

        list.add("1");

        list.add("1");

        //使用for循环删除 删不全,删除不了,不可取

        for (int i =0;i<list.size();i++){//删除之后,size为1,i也为1,所以直接退出循环

            if (list.get(i).equals("1")){

                list.remove(i);//删除一个之后,size变为1

            }

        }

//----------------------------------------

        List<String> list1 = new ArrayList<>();

        list1.add("1");

        list1.add("1");

        //1.8之前 使用迭代器删除 可以删除

        Iterator<String> iterator = list1.iterator();

        while (iterator.hasNext()){

            if (iterator.next().equals("1")){

                iterator.remove();

            }

        }

//-------------------------------------------------

        List<String> list2 = new ArrayList<>();

        list2.add("1");

        list2.add("1");

        //1.8 版本之后 使用removeIf  可以删除

        list2.removeIf(s -> s.equals("1"));//底层实现就是迭代器

    }

Jdk1.8之前使用迭代器,1.8之后使用removeif函数。

 

说说你理解的fail-fast和fail-safe?

fail-fast是java一种快速判断是否存在多线程操作集合的一种机制。在使用迭代器对集合进行遍历过程中,一旦发现容器的数据被修改,就会抛出concurrentModificationException异常导致遍历失败。常见于hashmap和ArrayList的实现中。我们看下ListItr是怎么判断是否有修改。

private class Itr implements Iterator<E> {

    

int expectedModCount = modCount;//初始值相等



//快速判断主要在判断修改的数据和期待修改的是否一致

final void checkForComodification() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

}

}

expectedModCount在迭代器中除了将modCount的值赋值之外,没有其他的修改,那就意味着如果要不等,那就是modCount的值已经修改了,而modCount 的值修改意味着其他线程在调用list的add或者remove的操作。

Fail-safe是基于集合的克隆进行处理的,当容器的值修改之后,克隆的集合并不会受到影响。在concurrent包中基本都是fail-safe的。这个是一个cow(copy on write)原理的应用。其优点是不会触发concurrentModificationException,但是其遍历的是当时集合的一个快照,如果数据同时在这个时候添加就无法遍历了。

 

说说ArrayList的扩容机制?

直接上源码分析一下:

//入参:扩容之后的最小值

private void grow(int minCapacity) {

        // 原来的容量

        int oldCapacity = elementData.length;

//新的容量是原来的1.5倍,这里右移1位类似除以2.jdk源码有很多位运算

        int newCapacity = oldCapacity + (oldCapacity >> 1);

//1.5倍后比最小值还小,直接变成最小值

        if (newCapacity - minCapacity < 0)

            newCapacity = minCapacity;

// 是否大于定义的最大值,如果大于就是整型的最大值,否则就是MAX_ARRAY_SIZE

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

        // 数据拷贝,使用系统拷贝函数

        elementData = Arrays.copyOf(elementData, newCapacity);

    }

从源码上分析,扩容经过了几步判断:直接是原来的1.5 -》和入参比较-》和maxARRAY比较。

说说hashset是怎么保证元素唯一的?

这个我们还是直接从源码入手进行分析,我们先看下添加一个元素的流程。

public boolean add(E e) {

        return map.put(e, PRESENT)==null;//如果存在,返回oldvalue,不存在,返回null

}

//底层是hashmap,我们看下hashmap的实现,value是一个固定的object

public V put(K key, V value) {

        return putVal(hash(key), key, value, false, true);

}

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

    boolean evict) {

//...

//判断是否存在,hash 值和equals的方法都要相等

if (p.hash == hash &&

                ((k = p.key) == key || (key != null && key.equals(k))))

                e = p;

//...    

}

从上面的流程看出,如果hash和equals都相等的话,那就认为是存在了。这个会在 hashmap中重点说明一下putval方法。

 

comparable 和 Comparator的区别是什么?

Comparator在包java.util下,而Comparable在包java.lang下。Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序。Comparble是一个对象支持的比较需要实现的接口,比如string,Byte都有实现这个接口。当一个对象定义的比较方法无法满足要求时,这个时候,我们可以考虑实现comparator接口,这就是两种的区别所在。

下面我们举个例子来说明一下:

public static void main(String[] args) throws Exception {

        Test test1 = new Test(1,2);

        Test test2 = new Test(2,1);

        Test[] testArray = new Test[]{test1,test2};

        Arrays.sort(testArray);//结果顺序test1,test2

        Arrays.sort(testArray,new ComparatorTest());//结果顺序test2,test1

    }

    //定义新的比较函数,不用去修改类

    public static class  ComparatorTest implements Comparator<Test>{

        @Override

        public int compare(Test o1, Test o2) {

            return o1.b.compareTo(o2.b);

        }

    }

    //定义类的比较函数

  public static class  Test implements Comparable<Test>{

        private Integer a;

        private Integer b;

        Test(int a,int b){

           this.a = a;

           this.b = b;

        }



        @Override

        public int compareTo(Test o) {

            return this.a.compareTo(o.a);

        }

  }

在上面的例子中,定义一个test类,定义类的同时定义了comparable接口,实现类内比较。又定义了一个ComparatorTest类,用于那些类无法满足的比较器。

 

本文介绍的集合侧重点在集合的collection,下一节我们会重点介绍map,尤其是hashmap。如果有时间的话,建议看下jdk源码的集合类,如果有问题的话,可以看下公众号里面的源码解析或者留言一起探讨哈。

 

本文的内容就这么多,如果你觉得对你的学习和面试有些帮助,帮忙点个赞或者转发一下哈,谢谢。

 

 

想要了解更多java内容(包含大厂面试题和题解)可以关注公众号,也可以在公众号留言,帮忙内推阿里、腾讯等互联网大厂哈

 

                                      

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值