Java集合之List集合详细解释
数组与集合
数组和集合都是存放 Java数据的 Java容器,数组与集合底层的数据结构都是线性结构,对于数组而言,一旦声明了长度,数据存放的容量就不能进行改变,集合就不一样,集合也是容器但是里面的数据容量却是可以改变的,不同的集合所存放的集合数据规则也就不一样。数组在删除,添加,修改操作方面的效率不太高,存储数据的特点是有序可重复的,但是对于无序,不可重复的需求来讲,数组就无法实现了,此时Java就引出了集合这个概念。Java中的集合类都放在了**java.util**包中
单列集合框架结构
上图中的集合主要就是三种Collection,List,Set集合,List,Set接口集合继承的是Collection单体集合接口,并拥有Collection中的接口方法。Collection单体集合主要存放一个一个的对象。
- List集合是有序可重复的,实现类主要有
ArrayList
,LinkedList
,Vector
- Set集合是无序不可重复,实现类主要有
HashSet
、LinkedHashSet
、TreeSet
Collection接口常用方法
①添加:
add(Object obj)
:只要是对象都可以添加
addAll(Collection c)
:添加另一个集合的元素
②删除:
clear()
:清空集合元素
remove(Object obj)
删除某个元素对象
removeAll(Collection c)
:删除集合元素
③判断:
isEmpty()
:集合是否为空
contains(Object c)
:是否包含某个元素
contains(Collection c)
:是否包含某个集合
④获取:
size()
:获取集合大小
Iterator<E> iterator()
:获取迭代器对象,遍历集合
⑤其他:
Object[] toArray()
:集合转数组
retainAll(Collection c)
:两个集合交集操作
使用这些方法:
Collection c = new ArrayList();
//添加集合元素
c.add("hello");
c.add("world");
c.add(199);//Integer.valueOf()
Object[] o = c.toArray();
System.out.println(Arrays.toString(o));
System.out.println(c.size());//3
Collection c1 = new ArrayList();
c1.add("hello");
c1.add("world");
//添加另一个集合元素
System.out.println(c);
c.addAll(c1);
System.out.println(c);//[hello, world, 199, hello, world]
//删除c集合中的hello,只会删除第一个
System.out.println(c.remove("hello"));
System.out.println(c);
c.clear();
System.out.println(c);// [] c集合已经清空
System.out.println(c.isEmpty());// true 是否为空
此段代码中,199并不是对象为什么能够添加呢?因为底层使用了自动装箱的操作Integer.valueOf()
的操作
重点来看一下集合retainAll(Collection c)
的方法,它是判断两个集合是否具有交集,使用方式就是oldCourse.retainAll(newCollection)
,如果oldCourse
发生了改变则为true,反之false
Collection c1 = new ArrayList();
c1.add("world");
c1.add("cv");
c1.add("xx");
c1.add("abf");
Collection c2 = new ArrayList();
c2.add("hello");
c2.add("world");
//集合取交集,判断c2是否发生了改变,改变返回true,没变返回false
boolean b1 = c2.retainAll(c1);
System.out.println(b1);
System.out.println(c1);//[world, cv, xx, abf]
System.out.println(c2);//[world]
**分析:**判断c2是否与c1有交集,发现c1与c2具有交集“world”,则c2发生了改变,输出的b1为true,c1并不会发生改变,如果没有交集的话,c2会变为空[],c1还是原始的数据。
List集合接口
常用方法
-
增:add(Object obj)
-
删:remove(int index)或remove(Object obj)
-
改:set(int index, Object ele)
-
查:get(int index)
-
插:add(int index, object ele)
-
长度:size()
-
遍历:① Iterator迭代器方式
② 增强for循环
③ 普通的循环
List list = new ArrayList();
//添加
list.add("a");
list.add("b");
list.add("c");
list.remove(0);//删除第一个元素
list.set(0, "a");//修改第一个元素
System.out.println(list.get(list.size()-1));//得到最后一个元素
list.add(2, "d");//在索引位置为2的位置插入元素d
System.out.println(list);//a,c,d
重点来看集合遍历的三种方式
-
① Iterator迭代器遍历的方式:
List list = new ArrayList(); //添加 list.add("a"); list.add("b"); list.add("c"); Iterator it = list.iterator(); while(it.hasNext()) { String s = (String) it.next(); System.out.println(s); }
原理:
图中可以看出从a开始查,有元素就next(),没有元素则停止while循环
-
② 增强for循环实现
List<String> list = new ArrayList<>(); //添加 list.add("a"); list.add("b"); list.add("c"); for(String i: list) { System.out.println(i); }
-
③ 普通的循环
List<String> list = new ArrayList<>(); //添加 list.add("a"); list.add("b"); list.add("c"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }
同时我们也可以用```ListIterator``对象进行遍历:
这是
ListIterator
的正向遍历List<String> list = new ArrayList<>(); //添加 list.add("a"); list.add("b"); list.add("c"); //正向遍历 ListIterator<String> lt = list.listIterator(); while(lt.hasNext()) { String s = lt.next(); System.out.println(s); }
这是
ListIterator
的逆向遍历List<String> list = new ArrayList<>(); //添加 list.add("a"); list.add("b"); list.add("c"); //正向遍历,如果没有这一步逆向遍历会为空,因为逆向遍历获取的是前一个元素,a的前面没有元素,所以为空 ListIterator<String> lt = list.listIterator(); while(lt.hasNext()) { lt.next(); } //逆向遍历 while(lt.hasPrevious()) {//获取上一个元素 String s = lt.previous(); System.out.println(s);//输出的是cba }
ListIterator
与Iterator
的区别:
-
ListIterator
实现了Iterator
,并且还可以包括其它的增加元素,替换,获取前一个或者后一个元素的索引等 -
ListIterator
只能操作List,而Iterator
能操作Set和List -
ListIterator
不仅可以正向遍历而且还可以逆向遍历
看一个ListIterator
例子:
判断集合中是否存在“java”,如果存在就在里面添加“javaee”
下面提供二种方式:
第一种方法,直接for循环遍历
public static void main(String[] args) {
List<String> l = new ArrayList<>();
l.add("php");
l.add("java");
l.add("c");
check(l);
}
//第一种方法,直接for循环遍历
public static void check(List<String> list) {
for(int i = 0; i<list.size(); i++) {
if("java".equals(list.get(i))) {
list.add("javaee");
}
}
System.out.println(list);
}
第二种方法,使用迭代器进行遍历添加
public static void main(String[] args) {
List<String> l = new ArrayList<>();
l.add("php");
l.add("java");
l.add("c");
check02(l);
System.out.println(l);
}
public static void check02(List<String> list) {
ListIterator<String> li = list.listIterator();
while(li.hasNext()) {
String s = li.next();
if("java".equals(s)) {
list.add("javaee");
break;
}
}
}
如果把break
去掉的话,添加并不会成功,因为添加是用list集合添加的,事先迭代器并不知道list添加了数据,这个异常是并发修改异常ConcurrentModificationException
只要加个break
将程序进行阻断然后再次遍历就会添加成功,当然也可以用迭代器进行遍历,添加直接交给迭代器处理,改成li.add("javaee")
即可。
ArrayList
对于ArrayList.它的底层就是用数组进行实现的,因此它查询比较快,执行效率就会比较高,支持null值的存在,但是插入删除数据比较慢,还有它线程是不安全的,ArrayList继承AbstractList,实现List接口,它实现了Serializable接口,可以被序列化,实现RandomAccess实现随机访问,实现了Cloneable接口,能被克隆。
关于JDK7与JDK8的原码对比
JDK7:
-
初始化时,会默认创建容量为10 的数组
ArrayList list = new ArrayList();
private static final int DEFAULT_CAPACITY = 10
-
当添加的数据超出容量为10时,就会实现自动扩容,扩容默认是原来的1.5倍,同时将原来的数组复制到新扩容的数组中。
JDK8:
- 底层Object[] elementData初始化为{}.并没创建长度为10的数组
- 第一次调用的时候再创建容量为10的数组,后续的添加与操作和JDK7无异
LinkedList
LinkedList是双向链表,底层是用链表进行实现的,特点就是删除添加快,效率高,查询比较慢,同样它也是线程不同步,不安全的。提供方法可以访问第一个和最后一个元素。
Vector
底层使用数组进行实现的,线程是安全的,查询块,添加删除慢,效率低下。jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
在开发中基本不去使用,但是得知道!
特有功能:
-
addElement(E obj):添加元素
-
E elementAt(int index):指定索引位置处对应的元素值
-
Enumeration elements():获取集合所元素构成的一个枚举对象
Enumeration里面方法
hasMoreElements():是否更多元素
nextElement():获取下个元素Vector<String> vector = new Vector<>(); vector.add("a"); vector.add("b"); vector.add("c"); //获取首元素 System.out.println("首元素:"+ vector.elementAt(0)); //获取集合元素构成的枚举对象 Enumeration<String> enumeration = vector.elements(); while(enumeration.hasMoreElements()) { String s = enumeration.nextElement(); System.out.println(s); }
关于对List集合遍历的补充说明:
- 对于实现了RandomAccess接口的List,首选for循环,后选foreach
- 未实现RandomAccess接口的List,首选Iterator迭代器进行遍历,对于size比较大的,千万不要使用普通for循环
Queue队列
java中也提供了队列的实现方式,它是一种特殊的线性表,从一端进行插入称为入队,另一端取出数据称为出队,数据遵循先进先出(FIFO)规则,如果想要获取到后来的数据,就必须先获取到先入队的数据。
主要的成员方法:
- boolean offer(E e):向队列末尾添加元素(入队)
- E poll():获取并删除队列的首元素(出队)
- E peek():获取队列的首元素
使用这些方法:
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("a");
queue.offer("b");
queue.offer("c");
System.out.println("首元素为:"+queue.peek());//a
System.out.println(queue.poll());//返回第一个元素并删除a
System.out.println(queue.element());//返回第一个元素b
for (String string : queue) {
System.out.println(string);
}//b,c
//删除队列操作
while(!queue.isEmpty()) {
System.out.println(queue.poll());
}
System.out.println(queue);//[]
}
看看add与offer,remove与poll,peek与element的区别?
-
add与offer都是表示添加,有的队列就会有大小的限制,使用add就会报出异常,使用offer只会返回false
-
当队列为空值remove会报出异常,poll会返回null,peek与element方法也是一样的
Deque队列
属于双向队列,对于两端都可以进行相应的操作,如果只是在一端进行操作就变成了栈(先进后出)
Deque<String> deque = new LinkedList<>();
deque.push("a");
deque.push("b");
deque.push("c");
deque.push("d");
System.out.println("首元素为:" + deque.peekFirst());
//移除所有元素
while(!deque.isEmpty()) {
deque.pop();
}
System.out.println(deque);
for (String string : deque) {
System.out.println(string);
}
上一篇: 八皇后问题(递归回溯)
下一篇: 八皇后问题—Java—递归回溯
推荐阅读
-
Java 中初始化 List 集合的 6 种方式!
-
Java8利用stream的distinct()方法对list集合中的对象去重和抽取属性去重
-
java集合 ArrayDeque源码详细分析
-
Java自学-集合框架 List接口
-
List、Set集合系列之剖析HashSet存储原理(HashMap底层)
-
Java8新特性(三)集合之 Stream 流式操作
-
详细分析Java并发集合LinkedBlockingQueue的用法
-
详细分析Java并发集合ArrayBlockingQueue的用法
-
Java中的容器(集合)之ArrayList源码解析
-
Java中的容器(集合)之HashMap源码解析