JAVA中的List的使用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA中的List的使用相关的知识,希望对你有一定的参考价值。
List<E>([]内的内容可省略),与数组类似:实例化:List[<数据类型>] list = new ArrayList[<数据类型>]();
获得集合内元素个数:list.size();
添加元素:
默认添加:list.add(e);
指定下标添加(添加后下标后的元素向后挪一位):list.add(index,e);
删除元素:
返回是否删除:list.remove(e);
直接删除指定下标的元素(只删除找到的第一个相符合的元素):list.remove(index);
替换元素(替换掉指定下标的元素):list.set(index,e);
取出元素:list.get(index);
清空集合:list.clear();
判断集合中是否存在某个元素(存在返回true,不存在返回false):list.contains(e);
对比两个集合中的所有元素:
两个对象一定相等:list.equals(list2);
两个对象不一定相等:list.hashCode() == list2.hashCode();
(两个相等对象的equals方法一定为true, 但两个hashcode相等的对象不一定是相等的对象。)
获得元素下标:
元素存在则返回找到的第一个元素的下标,不存在则返回-1:list.indexOf(e);
元素存在则返回找到的最后一个元素的下标,不存在则返回-1:list.lastIndexOf(e);
判断集合是否为空(空则返回true,非空则返回false):list.isEmpty();
返回Iterator集合对象:list.iterator();
将集合转换为字符串:list.toString();
截取集合(从fromIndex开始在toIndex前结束,[fromIndex,toIndex)):list.subList(fromIndex,toIndex);
将集合转换为数组:
默认类型:list.toArray();
指定类型(objects为指定类型的数组对象,并将转换好的数组赋值给objects数组):list.toArray(objects);
以上为List常用的方法。 参考技术A import java.util.*;
public class ListIteratorTest
public static void main(String[] args)
List list = new ArrayList();
//添加数据
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println("下标0开始:"+list.listIterator(0).next());//next()
System.out.println("下标1开始:"+list.listIterator(1).next());
System.out.println("子List 1-3:"+list.subList(1,3));//子列表
ListIterator it = list.listIterator();//默认从下标0开始
//隐式光标属性add操作 ,插入到当前的下标的前面
it.add("sss");
//循环输出数据
while(it.hasNext())
System.out.println("next Index="+it.nextIndex()+",Object="+it.next());
//set属性
ListIterator it1 = list.listIterator();
it1.next();
it1.set("ooo");
ListIterator it2 = list.listIterator(list.size());//下标
while(it2.hasPrevious())
System.out.println("previous Index="+it2.previousIndex()+",Object="+it2.previous());
参考技术B 你学习数据结构就知道怎样使了。 。
debug底层java代码,对list中的数据去重的正确姿势,及对比java list remove正确使用方法与错误使用
前言
List中主要有2种数据结构的实现,一种是数组,另一种是链表。
数组,均实现RandomAccess类
数组的实现有ArrayList、Vector、Stack、CopyOnWriteArrayList他们都继承了AbstractList,AbstractList实现了List中的部分接口,并且实现了数组结构的遍历,也就是上边说到的迭代器设计模式中Iterator部分,Stack实现了一个LIFO,Vector实现了线程安全,CopyOnWriteArrayList则是线程安全的一个变体,读的操作没有锁性能会更好,而写的时候存在锁并且在写的时候copy元数据,在新的数据上做修改,然后在同步回元数据,从而实现了在遍历的时候,可以对其进行修改而不会抛出CurrentModificationException异常。
链表,未实现RandomAccess类
LinkedList底层使用Entry类来存放数据元素。链表,就是手拉手,通过一个元素可以知道上一个元素和下一个元素,链表的遍历方式实现和数组是不一样的, 构造方法中初始化了next的值,if(int < (size >> 1))这里介绍下>>和>>>符号,>>代表向右移位。
例如3>>1则代表二进制0011向右移动1位,则是0001最终结果就是1,8>>>3代表8除以2的3次方最终结果则是1。
这里的if(int < (size >> 1)),主要是为了判断这个元素时在前半段还是在后半段,size >> 1相当于size/2,主要是为了更快的定位链表中第index元素的位置,不用从头遍历,这就是迭代器设计模式的好处,隐藏了内部实现的细节,对不同的数据结构提供了统一的迭代接口。
准备测试list的主方法
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
list.add("shanghai");
list.add("shanghai");
list.add("guangzhou");
list.add("shenzhen");
list.add("shanghai");
list.add("hangzhou");
list.add("shanghai");
list.add("beijing");
list.add("shanghai");
list.add("shanghai");
list.add("guangzhou");
list.add("shenzhen");
list.add("shanghai");
list.add("hangzhou");
list.add("shanghai");
}
// print(list);
System.out.println("\\n");
removeSetRepeat(list); // 9ms 25ms
// removeStrRepeat(list); //19ms 78ms
System.out.println("\\n");
// removeRowRepeat(list); //9ms 24ms
}
private static void print(List<String> list) {
System.out.print("item:");
for (String item : list) {
System.out.print(item+":");
}
}
for循环去重对比
StringBuilder拼接去重
/*
* 正确 StringBuilder去重
*/
public static void removeStrRepeat(List<String> list){
StopWatch sw = new StopWatch();
sw.start();
StringBuilder stringBuilder = new StringBuilder("x");
for(int i = list.size() - 1; i >= 0; i--){
String item = list.get(i);
if(item == null || stringBuilder.toString().contains(item)){
list.remove(i);
continue;
}
stringBuilder.append(item).append("x");
}
print(list);
sw.stop();
System.out.println("----:" + sw.getTotalTimeMillis()+"ms");
}
StringBuilder去重 14万条数据19ms,140万条数据 78ms
ArrayList去重
/*
* 正确 ArrayList去重 14万条数据9ms,140万条数据 24ms
*/
public static void removeRowRepeat(List<String> list){
StopWatch sw = new StopWatch();
sw.start();
List<String> rowx = new ArrayList<>();
for(int i = list.size() - 1; i >= 0; i--){
String item = list.get(i);
if(item == null || rowx.contains(item)){
list.remove(i);
continue;
}
rowx.add(item);
}
print(list);
sw.stop();
System.out.println("----:" + sw.getTotalTimeMillis()+"ms");
}
ArrayList数组去重 14万条数据9ms,140万条数据 24ms。
set去重
/*
* 正确 HashSet去重 14万条数据9ms,140万条数据 25ms
*/
public static void removeSetRepeat(List<String> list){
StopWatch sw = new StopWatch();
sw.start();
Set<String> set = new HashSet<>(list);
print(new ArrayList<>(set));
sw.stop();
System.out.println("----:" + sw.getTotalTimeMillis()+"ms");
}
Set去重 14万条数据9ms,140万条数据 25ms,与ArrayList数组for循环去重不相上下。
for循环size类型的remove
错误案例
IndexOutOfBoundsException索引越界
public static void remove11(List<String> list, String target) {
int size = list.size();
for (int i = 0; i < size; i++) {
String item = list.get(i);
if (target.equals(item)) {
list.remove(item);
}
}
print(list);
}
index从0开始,remove不能完全删除目标关键词shanghai----逆行撞车
由于index一直增加,而list中删除一次,list的size减小,导致list.get(i)可能忽略其中一个元素
/*
* 错误 index从0开始,remove不能完全删除目标关键词shanghai
* 由于index一直增加,而list中删除一次,list.get(i)可能忽略其中一个元素
*/
public static void remove12(List<String> list, String target) {
//item:beijing:shanghai:shanghai:guangzhou:shenzhen:hangzhou:
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
System.out.println("----:" + item);
if (target.equals(item)) {
list.remove(item);
}
}
print(list);
}
正确案例
相向而行--list.remove,不会出错
size 倒着衰减,index 也是从最大值开始衰减,两者相向而行,故list.remove,不会出错
public static void remove13(List<String> list, String target) {
int size = list.size();
for (int i = size - 1; i >= 0; i--) {
String item = list.get(i);
if (target.equals(item)) {
list.remove(item);
}
}
print(list);
}
public static void remove14(List<String> list, String target){
for(int i = list.size() - 1; i >= 0; i--){
String item = list.get(i);
if(target.equals(item)){
list.remove(item);
}
}
print(list);
}
对象遍历list--foreach 写法--for(String item : list)
错误-->for(String item : list)进行remove造成ConcurrentModificationException
对象遍历list,list.remove,造成list 对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,因此抛出了ConcurrentModificationException异常。
/*
* 错误 对象遍历list,list 对象的 modCount 值进行了修改,而 list 对象的迭代器的 expectedModCount 值未进行修改,因此抛出了ConcurrentModificationException异常。
*/
public static void remove21(List<String> list, String target){
for(String item : list){
System.out.print(item+":");
if(target.equals(item)){
list.remove(item);
}
}
print(list);
}
正确-->CopyOnWriteArrayList
解决了 List的并发问题
public static void remove22(ArrayList<String> list, String target) {
final CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<String>(list);
for (String item : cowList) {
if (item.equals(target)) {
cowList.remove(item);
}
}
print(cowList);
}
每个CopyOnWriteArray List对象,里面有一个array 数组对象用来存放具体元素, ReentrantLock 独占锁对象,用来保证同时只有一个线程 对 array进行修改。
并发包中的并发List只有CopyOnWriteArrayList,CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作,都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略(add/set/remove(修改)时,复制新数组进行操作)。
原理:采用每add一个元素,则复制出一个新数组,此时如果多线程下,add/set/remove操作时,用新数组,get操作用旧数组,这样就不用对get进行加锁,但add/set/remove还是要加锁的(独占锁lock)。此时就形成了所谓的读写分离的状态。
CopyOnWriteArrayList的复制操作,导致了其存在以下问题:
- 存在内存占用的问题,因为每次对容器结构进行修改的时候,都要对容器进行复制,这么一来我们就有了旧有对象和新入的对象,会占用两份内存。如果对象占用的内存较大,就会引发频繁的垃圾回收行为,降低性能;
- CopyOnWrite只能保证数据最终的一致性,不能保证数据的实时一致性。
比如:一个线程正在对容器进行修改,另一个线程正在读取容器的内容,这其实是两个容器数组,那么读线程读到的是旧数据
所以CopyOnWriteArrayList 集合比较适用于读操作远远多于写操作的场景。
list.iterator迭代遍历
错误案例 list.iterator删除报ConcurrentModificationException
/*
* 错误案例 list.iterator删除报ConcurrentModificationException
*/
public static void remove31(List<String> list, String target){
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
if (item.equals(target)) {
list.remove(item);
}
}
print(list);
}
产生java.util.ConcurrentModificationException异常。list.iterator写法实际上是对的 Iterable、hasNext、next方法的简写。因此我们从list.iterator()着手分析,跟踪iterator()方法,该方法返回了 Itr 迭代器对象。
正确案例--用list.iterator中的内部类Itr进行删除
/*
* 正确 用list.iterator中的内部类Itr进行删除
*/
public static void remove32(List<String> list, String target){
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
String item = iter.next();
if (item.equals(target)) {
iter.remove();
}
}
print(list);
}
用list.iterator中的内部类Itr进行删除,不会修改ArrayList的modCount的值,故而modCount != expectedModCount永远为false
以上是关于JAVA中的List的使用的主要内容,如果未能解决你的问题,请参考以下文章
debug底层java代码,对list中的数据去重的正确姿势,及对比java list remove正确使用方法与错误使用