前言:
本章节主要学习Java集合框架,收录于Java基础系列,该系列主要讲解Java基础语法,面向对象以及Java高级部分知识点,希望可以帮助童鞋们。后续会持续更新分享JavaWeb系列和框架系列,项目系列,欢迎童鞋们互相交流。如果觉得不错的话可以三连订阅喔。
目标:
[1. 框架概念](#1. 框架概念)
[2. List](#2. List)
[2.1 ArrayList](#2.1 ArrayList)
[2.2 LinkedList](#2.2 LinkedList)
[3. Set](#3. Set)
[3.1 HashSet](#3.1 HashSet)
[3.2 TreeSet](#3.2 TreeSet)
[4. Map](#4. Map)
[4.1 HashMap](#4.1 HashMap)
[4.2 TreeMap](#4.2 TreeMap)
[4.3 Hashtable](#4.3 Hashtable)
[4.5 Properties](#4.5 Properties)
[5. Stack](#5. Stack)
[6. Collections和Arrays](#6. Collections和Arrays)
[6.1 Collections](#6.1 Collections)
[6.2 Arrays](#6.2 Arrays)
内容:
1. 框架概念
为保持一组相同类型的对象,以前的选择是数组。尤其是基本类型的内容。但是使用数组面临着在无法确定数组具体长度情况下难以处理的问题。并且有时候我们需要使用比较复杂的格式来保存对象。为了解决这个问题,java提供了集合类库。
各种集合的特点:
List:必须保存元素的特定顺序。
Set:不能有重复元素。
Map:一组成对的键值对对象。
Collection接口是Set和List接口的父接口,其中的主要方法如下:
集合分类
2.List
List是一个接口,里面包含的是有顺序元素的集合。List接口继承于Collection接口,并添加了一些方法。List的实现有两种:ArrayList和LinkedList。ArrayList采用动态数组的方式作为底层的实现,而LinkedList采用链表的方式实现。
2.1 ArrayList
ArrayList封装了可以动态增长的数组,即数组可以根据需要进行动态长度的再分配。我们可以通过Iterator和get方式来访问定位ArrayList中的数据。ArrayList比LinkedList具有更高的定位具体位置元素的效率。
java
package com.qls.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class TestArrayList {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
for(int i=0;i<100;i++){//给ArrayList添加元素
list.add(new Random().nextInt());//自动疯转
}
Iterator<Integer> iter = list.iterator();
while(iter.hasNext()){//采用iterator遍历
System.out.println(iter.next());
}
for(int i=0;i<list.size();i++){//采用索引遍历
System.out.println(list.get(i));
}
for(Integer var : list){//采用增强for循环遍历
System.out.println(var);
}
for(int i=0;i<50;i++){//删除50个元素
list.remove(i);
}
System.out.println(list.size());
}
}
2.2 LinkedList
LinkedList底层采用链表的方式实现,可以高效率的删除数据。
java
package com.qls.collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class TestLinkedList {
public static void main(String[] args) {
List<String> linkedList = new LinkedList<String>();
linkedList.add(0,"abc");
linkedList.add(1,"bcd");
linkedList.add(2,"def");
linkedList.add(3,"efg");
linkedList.add(4,"fgh");
if(linkedList.contains("abc")){
linkedList.remove("abc");
}
System.out.println(Arrays.toString(linkedList.toArray()));
//[bcd, def, efg, fgh]
List<String> arrList = new ArrayList<String>();
arrList.add("ghi");
arrList.add("hij");
arrList.add("ijk");
arrList.add("jkl");
arrList.add("klm");
linkedList.addAll(arrList);
System.out.println(linkedList);
//[bcd, def, efg, fgh, ghi, hij, ijk, jkl, klm]
if (!linkedList.isEmpty()) {
linkedList.clear();
}
System.out.println(linkedList.size());//0
}
}
3.Set
Set是一个接口,继承于Collection类。并添加了一些方法。Set用来保存不可重复的数据,他包含两个实现:一个是无序的HashSet,一个是有序的TreeSet。
3.1 HashSet
对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单。
java
package com.qls.collection;
public class Person {
private String firstName;
private String lastName;
public Person() {
super();
// TODO Auto-generated constructor stub
}
public Person(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
@Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
java
package com.qls.collection;
import java.util.HashSet;
import java.util.Set;
public class TestHashSet {
public static void main(String[] args) {
Person person = new Person("zhang","1");
Set<Person> personSet = new HashSet<Person>();
personSet.add(person);
person = new Person("zhang","2");
personSet.add(person);
person = new Person("zhang","3");
personSet.add(person);
person = new Person("zhang","4");
personSet.add(person);
person = new Person("zhang","5");
personSet.add(person);
person = new Person("zhang","6");
boolean result =personSet.add(person);
System.out.println("第一次添加的结果:" + result);//true
result =personSet.add(person);
System.out.println("第二次添加的结果:" + result);//false
for(Person p:personSet){
System.out.println(p);
}
if(personSet.contains(person)){
personSet.remove(person);
}
System.out.println(personSet);
//[Person [firstName=zhang, lastName=1], Person [firstName=zhang,lastName=2], Person [firstName=zhang, lastName=5], Person [firstName=zhang,lastName=3], Person [firstName=zhang, lastName=4]]
}
}
3.2 TreeSet
TreeSet也是继承于Set接口,和HashSet不同的是TreeSet以有序的方式保存添加的数据。保存到TreeSet中的数据要实现Comparable接口,否则的话要提示异常。
java
package com.qls.collection;
public class Person implements Comparable<Person> {
private String firstName;
private String lastName;
public Person() {
super();
// TODO Auto-generated constructor stub
}
public Person(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
@Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
}
@Override
public int compareTo(Person o) {
if (this == o) {
return 0;
} else if (o != null && o instanceof Person) {
if (o.getFirstName().compareTo(this.getFirstName()) == 0
&& o.getLastName().compareTo(this.getLastName()) == 0) {
return 0;
} else {
return -1;
}
} else {
return -1;
}
}
}
java
package com.qls.collection;
import java.util.Set;
import java.util.TreeSet;
public class TestTreeSet {
public static void main(String[] args) {
Person person = new Person("zhang","1");
Set<Person> personSet = new TreeSet<Person>();
personSet.add(person);
person = new Person("zhang","2");
personSet.add(person);
person = new Person("zhang","3");
personSet.add(person);
person = new Person("zhang","4");
personSet.add(person);
person = new Person("zhang","5");
personSet.add(person);
person = new Person("zhang","6");
boolean result =personSet.add(person);
System.out.println("第一次添加的结果:" + result);//true
result =personSet.add(person);
System.out.println("第二次添加的结果:" + result);//false
for(Person p:personSet){
System.out.println(p);
}
if(personSet.contains(person)){
personSet.remove(person);
}
System.out.println(personSet);//[Person [firstName=zhang, lastName=5], Person [firstName=zhang, lastName=4], Person [firstName=zhang, lastName=3], Person [firstName=zhang, lastName=2], Person [firstName=zhang, lastName=1]]
}
}
4.Map
Map接口的实现类是通过键值对的方式来保存对象,以提高查询效率,他包含两个实现:无序的HashMap和有序的TreeMap。
4.1 HashMap
HashMap 采用 "Hash 算法"来决定每个元素的存储位置。
当程序执行 map.put("name", value); 时,系统将调用"name"的 hashCode() 方法得到其 hashCode 值------每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。
java
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) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
java
package com.qls.collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class TestHashMap {
public static void main(String[] args) {
Map<String,Integer> hashMap = new HashMap<String,Integer>();
hashMap.put("语文",80);
hashMap.put("数学",86);
hashMap.put("英语",76);
hashMap.put("化学",100);
hashMap.put("计算机",90);
for(Entry<String,Integer> entry : hashMap.entrySet()){//高效遍历方式
System.out.println(entry.getKey()+"=" + entry.getValue());
}
Iterator<String> iter = hashMap.keySet().iterator();
String key = "";
while(iter.hasNext()){//效率低
key = iter.next();
System.out.println(key +"=" + hashMap.get(key));
}
hashMap.remove("计算机");
System.out.println(hashMap);//{数学=86, 化学=100, 语文=80, 英语=76}
}
}
4.2 TreeMap
TreeMap的实现是红黑树算法的实现。
红黑树又称红-黑二叉树,它首先是一颗二叉树,它具备二叉树所有的特性。同时红黑树更是一颗自平衡的排序二叉树。
我们知道一颗基本的二叉树他们都需要满足一个基本性质--即树中的任何节点的值大于它的左子节点,且小于它的右子节点。按照这个基本性质使得树的检索效率大大提高。我们知道在生成二叉树的过程是非常容易失衡的,最坏的情况就是一边倒(只有右/左子树),这样势必会导致二叉树的检索效率大大降低(O(n)),所以为了维持二叉树的平衡,大牛们提出了各种实现的算法,如:AVL,SBT,伸展树,TREAP ,红黑树等等。
平衡二叉树必须具备如下特性:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。也就是说该二叉树的任何一个子节点,其左右子树的高度都相近。
红黑树顾名思义就是节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。对于一棵有效的红黑树二叉树而言我们必须增加如下规则:
1、每个节点都只能是红色或者黑色
2、根节点是黑色
3、每个叶节点(NIL节点,空节点)是黑色的。
4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这棵树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。所以红黑树它是复杂而高效的,其检索效率O(log n )。
java
package com.qls.collection;
import java.util.Map;
import java.util.TreeMap;
public class TestTreeMap {
public static void main(String[] args) {
Person person1 = new Person("马","1");
Person person2 = new Person("马","2");
Person person3 = new Person("马","3");
Person person4 = new Person("马","4");
Person person5 = new Person("马","5");
Map<String,Person> treeMap = new TreeMap<String,Person>();
treeMap.put("person1", person1);
treeMap.put("person2", person2);
treeMap.put("person3", person3);
treeMap.put("person4", person4);
treeMap.put("person5", person5);
System.out.println(treeMap);//{person1=Person [firstName=马, lastName=1], person2=Person [firstName=马, lastName=2], person3=Person [firstName=马, lastName=3], person4=Person [firstName=马, lastName=4], person5=Person [firstName=马, lastName=5]}
}
}
4.3 Hashtable
HashTable继承Dictionary类,实现Map接口。其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类。每个键和每个值都是一个对象。在任何一个 Dictionary 对象中,每个键至多与一个值相关联。Map是"key-value键值对"接口。
HashTable和HashMap存在很多的相同点,但是他们还是有几个比较重要的不同点。
第一:我们从他们的定义就可以看出他们的不同,Hashtable基于Dictionary类,而HashMap是基于AbstractMap。Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。
第二:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。如下:当HashMap遇到为null的key时,它会调用putForNullKey方法来进行处理。对于value没有进行任何处理,只要是对象都可以。
第三:Hashtable的方法是同步的,而HashMap的方法不是。所以有人一般都建议如果是涉及到多线程同步时采用Hashtable,没有涉及就采用HashMap,但是在Collections类中存在一个静态方法:synchronizedMap(),该方法创建了一个线程安全的Map对象,并把它作为一个封装的对象来返回,所以通过Collections类的synchronizedMap方法是可以我们你同步访问潜在的HashMap。
java
package com.qls.collection;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map.Entry;
public class TestHashtable {
public static void main(String[] args) {
Hashtable<String,String> table = new Hashtable<String,String>();
table.put("1","one");
table.put("2","two");
table.put("3","three");
table.put("4","four");
table.put("5","five");
table.put("6","six");
table.put("7","seven");
table.put("8","eight");
table.put("9","nine");
table.put("10","ten");
System.out.println(table);//{9=nine, 8=eight, 7=seven, 6=six, 5=five, 4=four, 3=three, 2=two, 10=ten, 1=one}
Collection<String> set = table.values();
System.out.println(set);
System.out.println(table.contains("one"));
System.out.println(table.containsKey("9"));
Enumeration<String> enumer = table.elements();
while(enumer.hasMoreElements()){//遍历Hashtable中的所有value
System.out.println(enumer.nextElement());
}
for(Entry<String,String> e:table.entrySet()){
System.out.println(e.getKey()+":" + e.getValue());
}
}
}
4.4 Enumeration
Enumeration是一个接口,包含两个方法,源代码如下:
java
package java.util;
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
4.5 Properties
Properties继承于Hashtable类,是老的集合类中使用最多的类。主要用来读取properties配置文件。(native2ascii)
test.properties
user=qls
password=123456
address=\u8d4n\u0cu4\u901a\ud237jk\u473hkj\u43ha\dksahdk2
java
package com.qls.collection;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
public class TestProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
File file = new File("test.properties");
Properties props = new Properties();
props.load(new FileInputStream(file));
String key = "";
Enumeration<?> enumer = props.propertyNames();
while(enumer.hasMoreElements()){
key = (String) enumer.nextElement();
System.out.println(key +"=" + props.getProperty(key));
}
Properties systemProps = System.getProperties();
systemProps.list(System.out);//列出系统的所有属性
}
}
5.Stack
Stack类是老的Vector类的子类, Vector类也是基于数组实现的队列,代码与ArrayList非常相似,只不过在可能发生线程安全的方法上加上了synchorized关键字,使得其执行的效率相比ArrayList就低了。
java
package com.qls.collection;
import java.util.Vector;
public class TestVector {
public static void main(String[] args) {
Vector<String> vector = new Vector<String>();
vector.addElement("abc");
vector.add("def");
vector.add("efg");
vector.add(2,"fgh");//指定索引位置添加,注意索引不能超过目前存在元素长度-1
for(int i=0;i<vector.size();i++){
System.out.println(vector.elementAt(i));
System.out.println(vector.get(i));
}
}
}
java
package com.qls.collection;
import java.util.Stack;
public class TestStack {
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
System.out.println("now the stack is " + isEmpty(stack));
stack.push("1");
stack.push("2");
stack.push("3");
stack.push("4");
stack.push("5");
System.out.println("now the stack is " + isEmpty(stack));
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.search("2"));
}
public static String isEmpty(Stack<String> stack) {
return stack.empty() ? "empty" : "not empty";
}
}
6.Collections和Arrays
6.1Collections
此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。它包含在 collection 上操作的多态算法,即"包装器",包装器返回由指定 collection 支持的新 collection,以及少数其他内容。
如果为此类的方法所提供的 collection 或类对象为 null,则这些方法都将抛出 NullPointerException。
此类中所含多态算法的文档通常包括对实现 的简短描述。应该将这类描述视为实现注意事项,而不是规范 的一部分。实现者应该可以随意使用其他算法替代,只要遵循规范本身即可。(例如,sort 使用的算法不一定是合并排序算法,但它必须是稳定的。)
此类中包含的"破坏性"算法,即可修改其所操作的 collection 的算法,该算法被指定在 collection 不支持适当的可变基元(比如 set 方法)时抛出 UnsupportedOperationException。如果调用不会对 collection 产生任何影响,那么这些算法可能(但不要求)抛出此异常。例如,在已经排序的、不可修改列表上调用 sort 方法可能会(也可能不会)抛出 UnsupportedOperationException。
java
package com.qls.collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestCollections {
public static void main(String[] args) {
int[] arr = new int[]{4,5,3,6,7,1,9,0};
List<Integer> lists = new ArrayList<>();
for(int i=0;i<arr.length;i++){
lists.add(arr[i]);
}
Collections.sort(lists);//排序
System.out.println(lists);//[0, 1, 3, 4, 5, 6, 7, 9]
//Collections.shuffle(lists);//打乱顺序
//System.out.println(lists);//[0, 9, 5, 1, 3, 7, 6, 4]
Collections.reverse(lists);
System.out.println(lists);//[9, 7, 6, 5, 4, 3, 1, 0](结果是在注释了shuffle后测试的)
}
}
Collections里面包含了很多对集合进行添加,填充,排序,搜索,线程安全化等相关的操作。
6.2Arrays
此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。
除非特别注明,否则如果指定数组引用为 null,则此类中的方法都会抛出 NullPointerException。
java
package com.qls.collection;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TestArrays {
public static void main(String[] args) {
int[] intArr = new int[]{21,2,44,6,12,3,55,667,87,12};
Arrays.sort(intArr);
System.out.println(Arrays.toString(intArr));//[2, 3, 6, 12, 12, 21, 44, 55, 87, 667]
Cat[] cats = new Cat[10];
for(int i=0;i<10;i++){
cats[i] = new Cat("cat"+i,i+2);
}
Arrays.sort(cats, Collections.reverseOrder());
System.out.println(Arrays.toString(cats));
Arrays.sort(cats,new Comparator<Cat>(){
@Override
public int compare(Cat o1, Cat o2) {
if(o1==null || o2==null){
return -1;
}
return o1.getAge() - o2.getAge();
}
});
System.out.println(Arrays.toString(cats));
List<Cat> catList = Arrays.asList(cats);
System.out.println(catList);
int[] newArr = Arrays.copyOf(intArr, intArr.length);
Arrays.sort(newArr);//进行二分搜索之前必须进行排序
System.out.println(Arrays.binarySearch(newArr, 55));//55在新数组的第七个位置,索引从0开始
}
}