聊聊ArrayList的问题

List这种数据结构可以说在我们的实际工作中经常会使用到,最常见的就是ArrayList和LinkedList两种实现类了,两种结构的操作看似相似,但是原理却差别巨大。关于这两者的差别,小编特意总结了一些干货和各位读者们进行分享。

 

ArrayList和LinkedList的区别?

 

通过阅读util包里面的源码可以很容易的看出两者的大致区别:

ArrayList是一种具有动态扩容特性且基于数组基础的数据结构,而LinkedList则是一种基于链表的数据结构。

 

在进行元素查找的时候适合用ArrayList进行操作,在进行元素的添加和删除的时候适合用LinkedList。由于ArrayList是采用数组作为数据结构的,因此在进行查找的时候只需要根据下标的索引进行判断即可。

 

LinkedList数据结构则是采用链表的结构进行设计的,因此在查找的时候需要进行逐一比较,所以效率会比较慢(并非是全链查询,而是采用了折半搜索的方式来进行优化)。在添加或者删除元素的时候,由于ArrayList需要进行元素的位置移动,而链表的移动和删除只需要将链表节点的头尾指针进行修改即可。

 

ArrayList的动态扩容有什么特点?

 

当我们在进行ArrayList的插入元素时候,相应的元素会被插入到动态数组里面,但是由于数组本身所能存储的数据量是有限制的,因此在插入数据的时候,需要进行相应的动态扩容,在看源码的时候,可以看到相应的代码部分:

add操作源码:

 

1   /**
 2     * Appends the specified element to the end of this list.
 3     *
 4     * @param e element to be appended to this list
 5     * @return <tt>true</tt> (as specified by {@link Collection#add})
 6     */
 7    public boolean add(E e) {
 8        ensureCapacityInternal(size + 1);  // Increments modCount!!
 9        elementData[size++] = e;
10        return true;
11    }

 

核心扩容部分的实现:

 1     private void ensureCapacityInternal(int minCapacity) {
 2        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 3            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 4        }
 5
 6        ensureExplicitCapacity(minCapacity);
 7    }
 8
 9    private void ensureExplicitCapacity(int minCapacity) {
10        modCount++;
11
12        // overflow-conscious code
13        if (minCapacity - elementData.length > 0)
14            grow(minCapacity);
15    }

 

ArrayList默认数组的大小为10,扩容的时候采用的是采用移位运算

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

这里也可以看出ArrayList的扩容因子为1.5。(4>>1 就相当于4除以2 即缩小了一半)。

 

什么时候会选择使用ArrayList

 

这又是一个大多数面试者都会困惑的问题。多数情况下,当遇到访问元素比插入或者是删除元素更加频繁的时候,应该使用ArrayList。另外一方面,当在某个特别的索引中,插入或者是删除元素更加频繁,或者压根就不需要访问元素的时候,不妨考虑选择LinkedList。

 

这里的主要原因是,在ArrayList中访问元素的最糟糕的时间复杂度是O(1),而在LinkedList中可能就是O(n)了。在ArrayList中增加或者删除某个元素时候,如果触发到了扩容机制,那么底层就会调用到System.arraycopy方法,如果有兴趣深入挖掘jdk源码的话,会发现这是一个本地调用方法,被native修饰,该方法会直接通过内存复制,省去了大量的数组寻址访问等时间,但是相比于LinkedList而言,在频繁的修改元素的情况下,选用LinkedList的性能会更加好一点。

 

 1  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
 2        @SuppressWarnings("unchecked")
 3        T[] copy = ((Object)newType == (Object)Object[].class)
 4            ? (T[]) new Object[newLength]
 5            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
 6        //复制集合
 7        System.arraycopy(original, 0, copy, 0,
 8                         Math.min(original.length, newLength));
 9        return copy;
10    }

 

如果读者有去学习过jvm的话,应该会对“内存碎片“这个名词比较熟悉。基于数组结构的数据在存储信息的时候都需要有连续的内存空间,所以如果当内存碎片化情况较为严重的时候,可能在使用ArrayList的时候会有OOM的异常抛出。

 

如何复制某个ArrayList到另一个ArrayList中去?

 

1.使用clone()方法,比如ArrayList newArray = oldArray.clone();

2.使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject);

3.使用Collection的copy方法。

关于list集合的拷贝问题在面试中可能还会引申出深拷贝和浅拷贝的对比,小编后期会去专门写一篇笔记做讲解。

 

请说说ArrayList、Vector和LinkedList的区别 

 

相同点

这三者都是单列集合Collection下List集合的实现类,所以他们的共同点,元素有序,允许重复元素 。
不同点
1.ArrayList和Vector底层都是数组实现,这样的实现注定查找快、增删慢 。
2.Vector支持线程同步,是线程访问安全的,ArrayList线程不安全 。
3.LinkedList底层是链表结构,查找元素慢、增删元素速度快,线程不安全。 

 

Collection和Collections的区别 

 

Collection是单列集合的父接口,翻译过来则是指容器的意思,常见的Set接口,List接口都是属于Collection接口下边的内容。

Collections是工具类,提供了关于集合的常用操作,例如,排序、二分法查找、反转元素等。

常用的一些Collections的api:

 

1   Collections.sort(list);
2   Collections.emptyList();
3   Collections.binarySearch(list,"your key");
4   Collections.copy(list,list1);
5   System.arraycopy(list,1,list1,2,1);

 

其实java里面已经默认提供好了很多有用的工具类给开发者们,我们除了要熟悉里面的api使用之外,还需要了解各种api的使用策略以及不同场景下的使用优势。

 

modCount参数的意义

在ArrayList设计的时候,其实还包含有了一个modCount参数,这个参数需要和expectedModCount 参数一起使用,expectedModCount参数在进行修改的时候会被modCount进行赋值操作,当多个线程同时对该集合中的某个元素进行修改之前都会进行expectedModCount modCount的比较操作,只有当二者相同的时候才会进行修改,两者不同的时候则会抛出异常。这个机制也被称之为fail-fast机制。

 

COW容器

jdk1.5之前,由于常用的ArrayList并不具有线程安全的特性,因此在1.5之后的并发包里面出现了CopyOnWrite容器,简称为COW。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

并发包juc里面的CopyOnWriteArrayList中,核心原理主要是通过加入了ReentrantLock来保证线程安全性,从而解决了ArrayList的线程安全隐患问题。

相应的add操作源码如下

 

 1    /**
 2     * Appends the specified element to the end of this list.
 3     *
 4     * @param e element to be appended to this list
 5     * @return {@code true} (as specified by {@link Collection#add})
 6     */
 7    public boolean add(E e) {
 8        final ReentrantLock lock = this.lock;
 9        lock.lock();
10        try {
11            Object[] elements = getArray();
12            int len = elements.length;
13            Object[] newElements = Arrays.copyOf(elements, len + 1);
14            newElements[len] = e;
15            setArray(newElements);
16            return true;
17        } finally {
18            lock.unlock();
19        }
20    }

 

 

如果读者对于ArrayList的具体实现充满好奇心的话,不妨可以试着自己边看源码边动手写一套相应的数据结构,相信坚持下来一定会大有收获。

 

如果各位对小编的文章感兴趣,不妨可以试试扫描下方二维码,我会定期更新关于java的干货文章,内容主要以精为主。

 

                                                                 

 

 

Java可以通过使用第三方库来生成微信群聊头像,以下是一个示例: 1. 添加依赖 在项目的pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>com.github.houbb</groupId> <artifactId>combine-image</artifactId> <version>0.0.3</version> </dependency> ``` 这个依赖是一个开源的Java库,用于将多张图片合并成一张图片。 2. 编写代码 下面是一个示例代码,将多张头像合并成一个微信群聊头像: ```java import com.github.houbb.combine.api.ICombineImage; import com.github.houbb.combine.core.impl.support.builder.CombineImageBuilder; import com.github.houbb.combine.core.impl.support.constant.CombineImageConst; import com.github.houbb.combine.core.impl.support.handler.CombineImageHandler; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; import java.util.List; public class WeChatGroupAvatarGenerator { /** * 生成微信群聊头像 * @param avatarFiles 头像文件列表 * @param outputFilePath 输出文件路径 * @param width 头像宽度 * @param height 头像高度 */ public static void generateGroupAvatar(List<File> avatarFiles, String outputFilePath, int width, int height) throws Exception { // 创建CombineImageBuilder对象 ICombineImage combineImage = CombineImageBuilder.newInstance() .setType(CombineImageConst.DEFAULT_TYPE) .setWidth(width) .setHeight(height) .setHandler(new CombineImageHandler() { @Override public BufferedImage handle(BufferedImage bufferedImage, int imageIndex) { return bufferedImage; } }) .build(); // 添加头像 for (File avatarFile : avatarFiles) { BufferedImage image = ImageIO.read(avatarFile); combineImage.addImage(image); } // 生成合并后的头像 BufferedImage groupAvatar = combineImage.combine(); // 保存合并后的头像 ImageIO.write(groupAvatar, "jpg", new File(outputFilePath)); } public static void main(String[] args) throws Exception { List<File> avatarFiles = new ArrayList<>(); avatarFiles.add(new File("avatar1.jpg")); avatarFiles.add(new File("avatar2.jpg")); avatarFiles.add(new File("avatar3.jpg")); avatarFiles.add(new File("avatar4.jpg")); avatarFiles.add(new File("avatar5.jpg")); generateGroupAvatar(avatarFiles, "group_avatar.jpg", 200, 200); } } ``` 这个示例中,首先创建一个CombineImageBuilder对象,并指定了头像的宽度和高度。然后遍历头像文件列表,将每个头像读取并添加到CombineImageBuilder中。最后调用combine方法生成合并后的头像,并保存到文件中。 注意:在实际应用中,应该根据头像数量来计算宽度和高度,以确保合并后的头像不会失真。同时,应该处理头像的边缘,以使合并后的头像看起来更自然。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值