Java集合是一种特别有用的工具,可用于存储不同的对象。并可以实现常用的数据结构如堆、栈、队列等。Java 集合还可以用于保存具有映射关系的数组。Java集合大致可以分为Set、List、Queue和Map四种体系。其中Set代表的是无序不可重复的集合,List代表的是有序且可以重复的即可,Map代表具有映射关系的集合,Queeu代表的是队列集合。
集合概述
集合类和数组不一样,数组既可以保存基本类型的值,也可以保存对象(虽然是保存对象的引用变量);而集合只能保存对象(实际上保存的时对象的引用变量)。Java集合主要是由两个接口派生出来的:Collection和Map。Java的集合类主要由两个接口派生:Collection和Map,他们是Java集合框架的根接口,这两个又包含了一些子类或实现类。
Collection接口、子接口及其实现的继承树:
Set集合的元素是无序的不可重复的、List集合元素有序可且重复、Map存放的是键值对。
Collection和Iterator接口
Collection接口是List、Set和Queue接口的父接口。
Collection接口操作集合的方法:
- boolean add(Object o):向集合里添加一个元素。如果集合对象被添加操作改变了,则返回true。
- boolean addAll(Collection c):和集合c所有元素添加到指定集合里。如果集合对象被添加操作改变了,则返回true。
- void clear():清楚集合里所有元素。
- boolean contains(Object o):返回集合里是否包含某个元素。
- boolean containsAll(Object o):返回集合里是否包含c里面所有元素。
- boolean isEmpty():返回集合是否为空,当集合长度为0返回true,否则返回flase。
- Iterator iterator():返回一个Iterator对象,用于遍历集合里面的元素。
- boolean remove(Object o):删除结合中的元素o,当包含多个o时,只删除第一个然后返回true。
- boolean removeAll(Collection c):从集合里删除集合c包含的所有元素,如果删除里一个或者一个以上,则返回true。
- boolean retainAll(Conllection c):从集合中删除c中不包含的元素,如果该操作改变了调用该方法的集合,则返回true。
- int size(): 返回集合里元素的个数。
- Obiect[] toArray():该方法把集合转换成一个数组,所有的元素都变成对应的数组元素。
上述方法的使用示例:
public class CollectionDemo {
@Test
public void collectionTest() {
Collection collection=new ArrayList();
System.out.println("添加元素add():");
collection.add("111");
collection.add(1);
for (Object o : collection) {
System.out.println(o);
}
System.out.println("addAll():");
Collection collection1=new ArrayList();
collection1.addAll(collection);
for (Object o : collection1) {
System.out.println(o);
}
System.out.println("删除指定元素remove():");
collection.remove(1);
for (Object o : collection) {
System.out.println(o);
}
System.out.println("清空所有元素clear():");
collection.clear();
for (Object o : collection) {
System.out.println(o);
}
System.out.println("是否包含指定元素contanins():");
System.out.println(collection1.contains(1));
System.out.println("集合是否为空isEmpty()");
System.out.println(collection.isEmpty());
System.out.println("转数组toArray()");
Object[] array = collection1.toArray();
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
输出结果:
添加元素add():
111
1
addAll():
111
1
删除指定元素remove():
111
清空所有元素clear():
是否包含指定元素contanins():
true
集合是否为空isEmpty()
true
转数组toArray()
111
1
使用Lambda表达式遍历集合
Java8为Iterator接口新增了一个foreach(Consumer action)默认方法,该方法所需要的参数类型是一个函数式接口,而Iterator接口是Collection接口的父接口,因此Collection集合也可以直接调用该方法。
使用Lambda表达式遍历集合元素的代码
public class LambdaDemo {
public static void main(String[] args) {
Collection collection=new HashSet();
collection.add("路飞");
collection.add("娜美");
collection.add("乔巴");
collection.add("布鲁克");
collection.forEach(o -> System.out.println("名称:"+o));
}
}
使用Iterator遍历结合元素
Iterator接口也是Java集合框架的成员,但它与Collection系列、Map系列的集合不一样:Collection系列集合、Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历Collection今个中的元素,Iterator也被称为迭代器。
Iterator接口定义的四个方法如下:
- boolean hasNext():如果被迭代的集合元素还没有被遍历完则返回true。
- Object next():返回集合里的在一个元素。
- void remove()删除集合里的下一个元素。
- void forEachRemaining(Consumer action),这是Java8 为Iterator新增的方法,该方法可以使用Lambda表达式遍历集合元素。
使用Iterator遍历的demo:
public class IetratorDemo {
public static void main(String[] args) {
Collection collection = new HashSet();
collection.add("路飞");
collection.add("娜美");
collection.add("乔巴");
collection.add("布鲁克");
String name;
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
name = iterator.next().toString();
System.out.println(name);
if (name.equals("路飞")) {
iterator.remove();//从集合中删除元素
}
}
collection.forEach(o -> System.out.println(o.toString()));
}
}
使用Lambda表达式遍历Iterator
当程调用Iterator的forEachRemaining()方法遍历集合时,程序依次将集合元素传给Consumner的Accept(T t)方法。
代码示例:
public class GetCollectionElements {
@Test
public void getCollectionElements() {
Collection partners = new HashSet();
partners.add("路飞");
partners.add("萨博");
partners.add("艾斯");
//这两种方式都不能给集合元素赋值
System.out.println("使用Lambda表达式遍历:");
partners.forEach(o ->System.out.print(o + " "));
System.out.print("使用迭代器遍历:");
Iterator iterator = partners.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
System.out.print("使用迭代器遍历2:");
Iterator iterator1 = partners.iterator();
iterator1.forEachRemaining(System.out::print);
}
}
使用foreach遍历集合
除了可以使用Itreator遍历集合之外,使用foreach也是不错的选择。
代码示例:
public class ForeachDemo {
public static void main(String[] args) {
Collection collection = new HashSet();
collection.add("路飞");
collection.add("娜美");
collection.add("乔巴");
collection.add("布鲁克");
String name;
for (Object o : collection) {
name= (String) o;
System.out.println("姓名:"+o);
}
}
}
使用Java8新增的Predicate操作集合
Java8为Collection集合新增了一个removeIf(Predicate filter)方法,该方法将会批量删除符合filter条件的所有元素。该方法还需要一个Predicate对象作为参数,Predicate也是函数式接口,因此也可以使用Lambda表达式作为参数。
示例代码;
@Test
public void predicateDemo2() {
Collection<String> singers = new HashSet<>();
singers.add("Taylor Swift");
singers.add("Justin Bibber");
singers.add("Zayn");
singers.add("Rahinna");
singers.add("Maroon");
singers.add("Linkin Park");
System.out.println(myFilter(singers, o -> ((String) o).contains("Z")));
System.out.println(myFilter(singers, o -> ((String) o).length()>5));
}
public int myFilter(Collection collection, Predicate predicate) {
int total=0;
for (Object o : collection) {
if(predicate.test(o))
total++;
}
return total;
使用Java8新增的Stream操作集合
Java8还新增了Stream、IntStream、LongStream、DoubleStream等流式API,这些API代表多个支持串行和并行聚集操作的元素。上面四个接口中,Stream是一个通用的流接口,而IntStream、LongStream、DoubleStream则代表int、long、double的流。
Java8还为上面每个流式api提供了对应的Builder,例如Stream.Builder、IntStream.Builder、LongStream.Builder、DoubleStream.Builder,可以通过这些Builder来创建对应的流。
使用步骤如下:
- 使用Stream或XXXStream的Builder()方法创建该Stream对应的Builder。
- 重复调用Builder的add()方法向该流添加多个元素。
- 调用Builder的build()方法获取对应的Stream。
- 调用Stream聚集方法。
注意:对于大部分聚集方法而言,每个Stream只能执行一次。
示例程序:
public class StreamDemo {
@Test
public void streamDemo() {
IntStream intStream = IntStream.builder()
.add(12)
.add(22)
.add(34)
.add(45)
.add(67)
.add(78)
.add(102)
.add(1123)
.build();
// System.out.println("最小值" + intStream.min());
// System.out.println("最大值" + intStream.max());
// System.out.println("元素和"+intStream.sum());
// System.out.println("平均数" + intStream.average());
System.out.println("是否都大于10" + intStream.allMatch(o -> Integer.valueOf(String.valueOf(o)) > 10));
}
@Test
public void arrayListStream() {
ArrayList arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
arrayList.add(5);
arrayList.add(6);
arrayList.add(7);
arrayList.add(8);
arrayList.add(9);
System.out.println(arrayList);
Stream stream= arrayList.stream();
System.out.println(stream.allMatch( o -> Integer.valueOf(String.valueOf(o)) > 1));
}
}
Stream提供了大量聚集方法,这些方式既可以是中间的,也可以是末端的:
- 中间方法:中间操作允许保持流的大开状态,并允许直接调用后续方法。中间方法的返回值是另一个流
- 末端方法:末端方法是对流的最终操作。当执行末端方法之后,该流将不再可用。
- 短路方法:短路方法遇到不符合条件的情况就不会在继续操作。
常见中间方法:
- filter(Predicate predicate): 过滤布不符合Predicate条件的元素。
常见末端方法:
- toArray():将流中所有元素转换为一个数组。
- min():返回最小值
- max():返回最大值
- count():流中所有元素的数量
- anyMatch(Predicate predicate): 是否至少有一个元素满足predicate条件。
- allMatch(Predicate predicate):是否所有元素都满足predicate条件。
Set集合
Set集合不能包含重复的元素,Set的add()的返回值是是否添加成功。
HashSet
HashSet是Set的典型实现,大多数时候使用Set集合的时候就是使用HashSet。HashSet按Hash算法来存储计算机中的元素,因此具有很好的存取和查找性能。
HashSet的特点:
- 元素没有顺序,可能与添加顺序不同,顺序也可能发生变化。
- 集合元素可以为空
- 不是同步的。如果多个线程同时访问该集合且有两个或者两个以上的线程修改了集合,必须通过代码来保证其同步。
下面以HashSet为例进行分析,我们都知道:在hashset中不允许出现重复对象,元素的位置也是不确定的。在hashset中又是怎样判定元素是否重复的呢?在java的集合中,判断两个对象是否相等的规则是:
- 判断两个对象的hashCode是否相等。如果不相等,认为两个对象也不相等,完毕;如果相等,转入2(这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。)
- 判断两个对象用equals运算是否相等。如果不相等,认为两个对象也不相等;如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。
重写hashCode()的原则:
- 在程序运行期间,同一个对象多次调用hashCode()结果以应该相同。
- 当两个对象通过equals()比较返回true时,其hashCode()也应该返回相等的值。
- 对象中用作equals()比较的实例变量都应该用于hashCode()计算。
hashCode值的计算方式:
实例变量类型 | 计算方式 |
---|---|
boolean | hashCode=(f?0:1); |
整数类型 | hashCode=(int)f; |
long | hashCode=(int)(f ^ (f >>> 32)); |
double | hashCode=Double.doubleToLongBits(f) ; |
float | hashCode=Float.floatToIntBits(f); |
引用类型 | hashCode=f.hashCode(); |
重写的例子:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
Person person = (Person) obj;
System.out.println("Fuck! Those two are same:"+this.getName() + "---" + person.getName());
return this.getName().equals(person.getName()) && this.getAge() == person.getAge();
}
@Override
public int hashCode() {
return name.hashCode() + age;
}
public String mToString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}' + getClass();
}
private String getType(Object object) {
return object.getClass().getName();
}
}
当不重写equals()和hashcode()时,为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有。因为在根据hashcode()对两次建立的new People(“11”,11)对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等。原因就在于我们自己写的Person类并没有重新自己的hashcode()和equals()方法,所以在比较时,是继承的object类中的hashcode()方法,而object类中的hashcode()方法是一个本地方法,比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样,所以Hashset会把它们当作不同的对象对待。
LinkedHashSet
LinkedHashSet是HashSet的一个子类,它使用了链表维护元素的顺序,使得元素表面上课是以插入的顺序保存的。LinkedHashSet在迭代访问全部元素的时候具有很好的性能,但是插入时性能稍微逊色于HashSet。
TreeSet
TreeSet是SortedSet接口的实现类,TreeSet可以保持元素的有序状态。
TreeSet特有的方法:
public class TreeSetDemo {
@Test
public void treeSetTest() {
TreeSet<Integer> set=new TreeSet<>();
set.add(6);
set.add(7);
set.add(8);
set.add(9);
set.add(10);
set.add(15);
set.add(14);
set.add(13);
set.add(12);
set.add(11);
Iterator<Integer> iterator=set.iterator();
iterator.forEachRemaining(System.out::println);
System.out.println();
System.out.println("第一个元素:"+set.first());
System.out.println("最后一个元素:"+set.last());
System.out.println("4之前的元素:"+set.lower(4));
System.out.println("4之后的元素:"+set.higher(4));
System.out.println("3到7之间的:"+set.subSet(3, 7));
System.out.println("小于3的所有:"+set.headSet(3));
System.out.println("大于3的所有:"+set.tailSet(3));
}
}
输出结果:
6
7
8
9
10
11
12
13
14
15
第一个元素:6
最后一个元素:15
4之前的元素:null
4之后的元素:6
3到7之间的:[6]
小于3的所有:[]
大于3的所有:[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
由此可见TreeSet并不是按照插入顺序排序的,而是按照元素的实际大小排序。TreeSet支持两种排序方法:自然排序和定制排序。
自然排序
TreeSet会调用集合元素的ccompareTo()方法来比较元素之间的大小关系并按从小到大的顺序排列。
如果向TreeSet添加的是自定义对象,那么这个对象必须实现Comparable接口并重写其compareTo(Object o)方法。具体规则如下:调用obj1.compareTo(obj2),但返回值为0是则则这两个对象相等,大于则第一个大,小于则第一个小。
public class TreeSetObjectSortedDemo {
public static void main(String[] args) {
TreeSet<People> peopleTreeSet=new TreeSet<>();
peopleTreeSet.add(new People(11,"bbb"));
peopleTreeSet.add(new People(11,"bbb"));
peopleTreeSet.add(new People(22,"ccc"));
peopleTreeSet.add(new People(22,"aaa"));
peopleTreeSet.forEach(System.out::println);
//People{age=11, name='bbb'}
//People{age=22, name='aaa'}
//People{age=22, name='ccc'}
}
}
public class People implements Comparable<People> {
private int age;
private String name;
@Override
public String toString() {
return "People{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public People() {
}
public People(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof People)) return false;
People people = (People) o;
if (getAge() != people.getAge()) return false;
return getName().equals(people.getName());
}
@Override
public int hashCode() {
int result = getAge();
result = 31 * result + getName().hashCode();
return result;
}
@Override
public int compareTo(People o) {
return (this.age==o.age)?this.name.compareTo(o.name):(this.age-o.age);
}
}
输出结果:
true
true
TreeSetClass{age=1}
TreeSetClass{age=1}
TreeSetClass{age=2}
TreeSetClass{age=2}
//虽然看上去是两个元素,但是这个元素实质指向的是同一个内存区域。所以修改其中一个元素师另一个元素也会跟着变化。
可以看出,对于TreeSet而言,他判断来那个对象相等与否的唯一条件就是来两个对象通过compareTo()方法比较之后的返回值是否为0,为0则相等。
需要注意的是,当向TreeSet添加一个可变对象并且后面修改了这个对象,修改之后这个对象的世纪位置是不是发生变化的。
定制排序
在创建集合的时候可以提供一个Comparator对象与该集合关联,并由Comparator对象负责对集合元素进行排序。
public class CustomSortedSet {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet((o1, o2) -> {
Data data1 = (Data) o1;
Data data2 = (Data) o2;
return data1.getAge() - data2.getAge();
});
// TreeSet treeSet = new TreeSet(new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// Data data1 = (Data) o1;
// Data data2 = (Data) o2;
// return data1.getAge() - data2.getAge();
// }
// });//这一段代码与上面使用Lambda表达式实现的效果是一样的
treeSet.add(new Data(22));
treeSet.add(new Data(11));
treeSet.add(new Data(9));
treeSet.add(new Data(232));
System.out.println(treeSet);
}
}
class Data {
private int age;
public Data() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Data(int age) {
this.age = age;
}
@Override
public String toString() {
return "Data{" +
"age=" + age +
'}';
}
}
EnumSet
使用很少。
public class EnumSetDemo {
public static void main(String[] args) {
EnumSet enumSet1=EnumSet.allOf(Seasons.class);
EnumSet enumSet2=EnumSet.noneOf(Seasons.class);
System.out.println(enumSet1);
enumSet2.add(Seasons.FALL);
enumSet2.add(Seasons.SPRING);
System.out.println(enumSet2);
}
}
public enum Seasons {
SPRING,SUMMER,FALL,WINTER;
}
各类Set的性能
HashSet的性能总是好过TreeSet,因为TreeSet需要额外的红黑树算法来维持集合的顺序。LinkedHashSet普通的插入删操作比HashMap,但是遍历时其性能好过HashSet。Set的三个实现类:TreeSet、HashSet、EnumSet都是线程不安全的。如果由多个线程同时操作这些集合,并且有一个以上的线程修改了集合,就需要手动保证这些集合的同步性。
List集合
List是一个有序可重复的集合,每个元素都有对应的索引。
Java8 改进的ListIterator接口
List有Collection接口的全部方法。因为List的有序的,他也有一些特殊的方法:ListIterator() 该方法返回一个ListIterator对象,ListIterator接口实现了Iterator接口,提供了专门的List的方法。
ListIterator特有的方法:
- boolean hasPrevious():返回与迭代器关联的集合是否还有上一个元素。
- Object previous():返回该迭代器的上一个元素。
- void add():在指定位置添加一个元素。
代码示例:
public class ListIteratorDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add(12);
list.add(14);
list.add(14);
list.add(16);
//List特有的ListIterator
ListIterator listIterator = list.listIterator();
listIterator.forEachRemaining(System.out::println);
System.out.println("正向迭代");
ListIterator listIterator1 = list.listIterator();
while (listIterator1.hasNext()) {
System.out.println(listIterator1.next());
listIterator1.add("------华丽的分隔符------");//使用ListIterator的add方法添加数据
}
System.out.println("反向迭代");//注意这里不需要再一个新的ListIterator实例,否则他前面是没有元素的。
while (listIterator1.hasPrevious()) {
System.out.println(listIterator1.previous());
}
}
}
ArrayList和Vector
ArrayList和Vector都是基于数组实现的,其内部都有一个动态的、允许再分配的Obiect[]数组。他们都使用initialCapacity来指定数数组长度,当超出长度时,initialCapacity会自动增加。
重新分配Object数组的方法:
- void ensureCapacity(int minCapacity): 将Object[]增加大于等于minCapacity的值。
- void trimToSize(): 将Object的长度变为当前元素个数
ArrayList和Vector的主要区别就是ArrayList是非线程安全的。
固定长度的List
Arrays.asList()的返回值一个固定长度的集合。该集合只能遍历其中的元素,不能增加删除。
Queue集合
Queue用于模拟队列,队列是先进先出(FIFO)。新元素是插入到队列尾部,访问元素是返回队列头部元素。通常队列不允许访问随机访问队列中的元素。
Queue常见方法:
- void add(Object obj):添加元素到队列尾部
- Object element():获取队列头部元素但不删除
- boolean offer(Object obj): 添加元素到队列尾部,容量有限制的队列这个方法好过add()
- Object peek():获取队列头部元素但不删除,如果队列为空则返回null
- Object poll():获取队列头部元素并删除该元素,如果队列为空则返回null
- Object remove():获取头部元素并删除
PriorityQueue集合
PriorityQueue是一个有序队列,所以当调用peek()或者poll()方法时返回的总是最小的。
public class PriorityQueueDemo {
public static void main(String[] args) {
PriorityQueue priorityQueue=new PriorityQueue();
priorityQueue.add(5);
priorityQueue.add(7);
priorityQueue.add(6);
priorityQueue.add(1);
priorityQueue.add(3);
int size=priorityQueue.size();
for (int i = 0; i < size; i++) {
System.out.println("size: " + priorityQueue.size()+" delete this:"+priorityQueue.poll());
}
}
}
输出结果为:
size: 5 delete this:1
size: 4 delete this:3
size: 3 delete this:5
size: 2 delete this:6
size: 1 delete this:7
值得注意的是如果直接输出PriorityQueue,可能看到的不是有序的输出,这是因为有PriorityQueue的toString()方法的影响,PriorityQueue也不允许插入null。
PriorityQueue的两种排序方式:
- 自然排序:PriorityQueue的元素实现Comparable接口
- 定制排序:创建PriorityQueue对象时传入一个Comparator对象。
Dqueq接口与ArrayDeque实现类
Dqueq是Queue的一个自接口,它代表一个双端队列。ArrayDeque和ArrayList的实现方式都是相似的,都是有一个动态的、可重新分配分Object[]数组。
栈的实现(先进后出):
public class ArrayDequeStackDemo {
public static void main(String[] args) {
ArrayDeque arrayDeque=new ArrayDeque();
arrayDeque.push(11);
arrayDeque.push(12);
arrayDeque.push(13);
arrayDeque.push(14);
System.out.println(arrayDeque);//[14, 13, 12, 11]
System.out.println();
System.out.println(arrayDeque.peek());//14
System.out.println(arrayDeque);//[14, 13, 12, 11]
System.out.println();
System.out.println(arrayDeque.poll());//14
System.out.println(arrayDeque);//[13, 12, 11]
System.out.println();
System.out.println(arrayDeque.poll());//13
System.out.println(arrayDeque);//[12, 11]
System.out.println();
System.out.println(arrayDeque.poll());//12
System.out.println(arrayDeque);//[11]
System.out.println();
System.out.println(arrayDeque.poll());//11
System.out.println(arrayDeque);//[]
System.out.println();
System.out.println(arrayDeque.poll());//null
System.out.println(arrayDeque);//[]
}
}
队列的实现(先进先出):
public class ArrayDequeQueuqDemo {
public static void main(String[] args) {
ArrayDeque arrayDeque=new ArrayDeque();
arrayDeque.offer(11);
arrayDeque.offer(12);
arrayDeque.offer(13);
arrayDeque.offer(14);
System.out.println(arrayDeque);//[11, 12, 13, 14]
System.out.println();
System.out.println(arrayDeque.peek());//11
System.out.println(arrayDeque);//[11, 12, 13, 14]
System.out.println();
System.out.println(arrayDeque.poll());//11
System.out.println(arrayDeque);//[12, 13, 14]
System.out.println();
System.out.println(arrayDeque.poll());//12
System.out.println(arrayDeque);//[13, 14]
System.out.println();
System.out.println(arrayDeque.poll());//13
System.out.println(arrayDeque);//[14]
System.out.println();
System.out.println(arrayDeque.poll());//14
System.out.println(arrayDeque);//[]
System.out.println();
System.out.println(arrayDeque.poll());//null
System.out.println(arrayDeque);//[]
}
}
LinkedList实现类
LinkedList是List的实现类,可以用索引来随机访问数据。此外,LinkedList还实现了Deque,也可以当作双端队列使用。ArrayList和ArrayDeque都是基于数组实现的,因此随机访问元素集合时性能较好;LinkedList则是基于链表实现的,其插入删除性能较好。虽然Vector也是基于数组实现的,但是由于实现了线程安全,所以各方面性能都不好。
各种线性表的性能分析
所有内部以数组作为底层实现的集合随机访问性能最好,内部以链表为底层实现的集合在执行插入删除时有较好的性能。
关于使用List集合有如下建议:
- 如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于LinkedList集合,则应该采用迭代器(Iterator)来遍历集合元素。
- 如果需要经常执行插入、删除操作来改变包含大量数据的List集合的大小,可考虑使用LinedList集合。使用ArrayList、Vector集合可能需要经常重新分配内部数组的大小,效果可能较差。
- 如果有多个线程需要同时访问List集合中的元素,开发者可考虑使用Collections将集合包装成线程安全的集合。
Map
Map用于保存具有映射关系的数据,Map的key不允许重复。Map的key集和和Set集合有很大的相似之处,vlaue集合和和List又非常相似。
Map接口常见用法示例以及说明:
public class MapMethodTest {
public static void main(String[] args) {
Map map = new HashMap();
map.put("船长", "路飞");
map.put("船医", "乔巴");
map.put("剑士", "索罗");
map.put("厨师", "山治");
map.put("历史学家", "妮可罗宾");
map.put("音乐家", "布鲁克");
map.put("船工", "弗兰奇");
map.put("航海士", "娜美");
map.put("狙击手", "乌索普");
//加入重复key时,新的vlaue会覆盖老value并返回老value
System.out.println(map.put("船长", "海贼王"));//路飞
//是否包含key值为XXX的条目
System.out.println(map.containsKey("船长"));//true
//是否有值为xxx的条目
System.out.println(map.containsValue("海贼王"));//true
//返回一个key-value组成的Set集合
Set set = map.entrySet();
System.out.println(set);//[剑士=索罗, 狙击手=乌索普, 船医=乔巴, 历史学家=妮可罗宾, 船长=海贼王, 航海士=娜美, 厨师=山治, 船工=弗兰奇, 音乐家=布鲁克]
//返回key值对应的value,不存在就返回空
System.out.println(map.get("航海士"));//娜美
//是否为空
System.out.println(map.isEmpty());//false
//返回key组成的Set集合,注意没有valueSet()这个方法
Set keySet = map.keySet();
System.out.println(keySet);//[剑士, 狙击手, 船医, 历史学家, 船长, 航海士, 厨师, 船工, 音乐家]
//移除key值为xxx的条目,如果不存在侧返回null
System.out.println(map.remove("卡普"));//null
//返回所有valuo组成的Collection
Collection collection = map.values();
System.out.println(collection);//[索罗, 乌索普, 乔巴, 妮可罗宾, 海贼王, 娜美, 山治, 弗兰奇, 布鲁克]
}
}
Map接口提供了大量实现类,例如HashMap和HashTable、HashMap的子类LinkedHashMap,SortedMap子接口以及该接口的实现类TreeMap。还有WeakHashMap以及IdentityHasHMap。
idea
Map有一个叫Entry的内部类,该类封装了一个key-value对,其主要方法说明及示例如下:
public class MapEntryDemo {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("船长", "路飞");
map.put("船医", "乔巴");
map.put("剑士", "索罗");
System.out.println("方法一:");
Iterator iterator=map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry<String, String> entry= (Map.Entry<String, String>) iterator.next();
System.out.println("key:"+entry.getKey()+" value"+entry.getValue());
}
System.out.println("方法二:");
for (Map.Entry<String, String> m : map.entrySet()) {
System.out.println("key:"+m.getKey()+" value"+m.getValue());
//设置value值
System.out.println(m.setValue("111"));
}
System.out.println(map);
}
}
Java8 新增的Map方法
后面说
Java8改进的HashMap和Hashtable类
HashMap是线程不安全的实现,Hashtable是线程安全的。如果多个线程同时访问同一个Map对象,Hashtable性能要好一些。HashTable不允许null的key和value,但是HashMap可以。因为Hashtable判断value是否相等是调用的value的equals()方法,所以如果重写了equals方法一直返回true或者flase会出现。
和HashSet一样,如果使用可变对象最为key值,当对象改变之后可能导致无法访问被修改过的key。
public class HashMapEqualsDemo {
public static void main(String[] args) {
HashMap objectObjectHashMap=new HashMap();
objectObjectHashMap.put(new HashClassA(1),"11");
objectObjectHashMap.put(new HashClassA(2),"11");
objectObjectHashMap.put(new HashClassA(3),"11");
objectObjectHashMap.put(new HashClassA(4),"11");
objectObjectHashMap.put(new HashClassA(5),"11");
System.out.println(objectObjectHashMap.size());//5
System.out.println(objectObjectHashMap);//{HashClassA{count=1}=11, HashClassA{count=2}=11, HashClassA{count=3}=11, HashClassA{count=4}=11, HashClassA{count=5}=11}
Iterator iterator =objectObjectHashMap.keySet().iterator();
HashClassA hashClassA=((HashClassA) iterator.next());
hashClassA.setCount(2);
System.out.println(objectObjectHashMap);//{HashClassA{count=2}=11, HashClassA{count=2}=11, HashClassA{count=3}=11, HashClassA{count=4}=11, HashClassA{count=5}=11}
System.out.println(hashClassA);//HashClassA{count=2}
objectObjectHashMap.remove(new HashClassA(2));
System.out.println(objectObjectHashMap);//{HashClassA{count=2}=11, HashClassA{count=3}=11, HashClassA{count=4}=11, HashClassA{count=5}=11}
//可以看到不能正常删除
}
}
所以尽量不要使用可变对象作为key值,如果必须要使用,也尽量不要修改。
LinkedHashMap
LinkedHashMap使用了双向链表维护元素的顺序,所以迭代出来的结果和插入顺序一致。LinkedHashMap插入性能略低于HashMap,但是其迭代性能更好。
使用Properties读写文件属性
Properties是Hashtable的子类,多用于处理属性文件。Properties可以把Map对象和属性文件关联起来,从而可以把Map的key-value对象写入文件属性。Properties的key和value都只能是String类型。
public class PropertiesDemo {
public static void main(String[] args) throws IOException {
Properties properties=new Properties();
properties.setProperty("username","xiongyu");
properties.setProperty("password","1234567");
// properties.setProperty("11",11);
// properties.setProperty(11,"11");
// 由于只能输入String,所以上面两行会报错。
// properties.put("test",11);//put没有限制添加类型,因此添加的时候不会报错。 存储的时候会报错 java.lang.ClassCastException
//
properties.store(new FileOutputStream("test.ini"),"说明");
Properties properties1=new Properties();
properties1.setProperty("gender","male");
properties1.load(new FileInputStream("test.ini"));
System.out.println(properties1);
}
}
操作集合的工具类Collections
Collections可对List进行排序
可以对集合进行查找、替换操作
同步控制
public class SynchronizedTest {
public static void main(String[] args) {
Collection collection= Collections.synchronizedList(new ArrayList());
List list=Collections.singletonList(new ArrayList());
Set s=Collections.synchronizedSet(new HashSet());
Map map=Collections.synchronizedMap(new HashMap());
}
}