1. Collection
1.1 List和set
在实际开发中,需要将使用的对象存储与特定数据结构的容器中。JDK提供了这样的容器——集合(Collection)。Collection是接口,定义了集合相关的操作方法,其有两个子接口:List与Set
- List:可重复集
1.2 集合持有对象的引用
集合中存储的都是引用类型元素,
并且集合只保存每个元素对象的引用,
而并非将元素对象本身存入集合。
public void testRef(){
Collection <Cell>cells = new ArrayList<Cell>();
cells.add(new Cell(1,2));
Cell cell = new Cell(2,3);
cells.add(cell);
System.out.println(cell); //(2,3)
System.out.println(cells); //[(1,2),(2,3)]
cell.drop();
System.out.println(cell); //(3,3)
System.out.println(cells) //[(1,2),(3,3)]
}
1.3 add方法
- Collection定义了一个add方法用于向集合中添加新元素。
- 该方法会将给定的元素添加进集合,若添加成功则返回true,否则返回false。
public void testAdd(){
Collection <String>c = new ArrayList<String>();
System.out.println(c); // []
c.add(“a”);
c.add(“b”);
c.add(“c”);
System.out.println(c); //[a,b,c]
}
1.4 contains方法
- boolean contains(Object o)
该方法会用于判定给定的元素是否被包含在集合中。若包含则返回true,否则返回false。
这里需要注意的是,集合在判断元素是否被包含在集合中是根据每个元素的equals()方法进行比较后的结果。
通常有必要重写equals()保证contains()方法的合理结果
public void testContains(){
Collection <Cell> cells = new ArrayList<Cell>();
cells.add(new Cell(1,2));
cells.add(new Cell(1,3));
cells.add(new Cell(2,2));
cells.add(new Cell(2,3));
Cell cell = new Cell(1,3);
//List集合contains方法和对象的equals方法相关
boolean flag = cells.contains(cell);
//如果Cell不重写equals方法将为false
System.out.println(flag); //true
}
1.5 Size,clear,isEmpty
int size()
该方法返回当前集合中的元素总数。
void clear()
该方法用于清空当前集合。
boolean isEmpty()
该方法用于判断当前集合中是否不包含任何元素
pulbic void testSizeAndClearAndEmpty(){
Collection<String>c = new HashSet<String>();
System.out.println(c.isEmpty()); //true
c.add("java");c.add("cpp");c.add("php");
c.add("c#");c.add("objective-c");
System.out.println("isEmpty:"+c.isEmpty()+",size:"+c.size())
//isEmpty:false,size:5
c.clear();
System.out.println("isEmpty:"+c.isEmpty()+",size:"+c.size());
//isEmpty:true,size:0
}
1.6 addAll,containsAll
boolean addAll(Collection<?extends E>c)
该方法需要我们传入一个集合,并将该集合中的所有元素添加到当前集合中。如果此collection由于调用而发生更改,则返回true
boolean containsAll(Colletion<?>c)
该方法用于判断当前集合是否包含给定集合中的所有元素,若包含则返回true。
public void testAddAndContainsAll(){
Collection<String>c1 = new ArrayList<String>();
c1.add(“java”); c1.add(“cpp”); c1.add(“php);
c1.add(“c#”); c1.add(“objective-c”);
System.out.println(c1); //[java,cpp,php,c#,objective-c]
Collection<String> c2 = new HashSet<String>();
c2.addAll(c1);
System.out.println(c2); //[cpp,php,c#,java,objective-c]
Collection<String>c3 = new ArrayList<String>();
c3.add(“java”); c3.add(“cpp”);
System.out.println(c1.containsAll(c3)); //true
}
2. Iterator
- 迭代器用于遍历集合元素。获取迭代器可以使用Collection定义的方法:
Iterator iterator()
迭代器Iterator是接口,集合在重写Collection的itrator()方法时利用内部类提供了迭代器的实现。
- Iterator提供了统一的遍历集合元素的方式,其提供了用于遍历集合的两个方法:
boolean hasNext():判断集合是否还有元素可以遍历。
E next():返回迭代的下一个元素
2.1 hasNext、next方法
public void testHasNextAndNext(){
Collection<String>c = new HashSet<String>();
c.add("java"); c.add("cpp"); c.add("php");
c.add("c#"); c.add("objective-c");
Iterator<String>it = c.iterator();
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
}
2.2 remove方法
- 在使用迭代器遍历集合时,不能通过集合的remove方法删除集合元素,否则会抛出并发更改异常。我们可以通过迭代器自身提供的remove()方法来删除通过next()迭代出来的元素。
void remove(); - 迭代的删除方法是在元集合中删除元素。
- 这里需要注意的是,在调用remove方法前必须通过迭代器的next()方法迭代过元素,那么删除的就是这个元素。并且不能再次调用remove方法,除非再次调用next()后方可再次调用。
public void testRemove(){
Collection<String>c = new HashSet<String>();
c.add("java"); c.add("cpp");
c.add("php"); c.add("c#");
c.add("objective-c");
System.out.println(c); //[cpp,php,c#,java,objective-c]
Iterator<String> it = c.iterator();
while(it.hasNext()){
String str = it.next();
if(str.indexOf(‘c’)!=-1){
it.remove();//删除包含字母c的元素
}
}
System.out.println(c) //[php,java]
}
3.增强型for循环
- Java5.0之后推出了一个新的特性,增强for循环,也称之为新循环。该循环不同于传统循环的工作,其只用于遍历集合或数组。
- 语法:
for(元素类型 e:集合或数组){
循环体
}
- 新循环并非新的语法,而是在编译过程中,编译器会将新循环转换为迭代器模式。所以新循环本质上是迭代器。
public void testForeach(){
Collection <String>c = new HashSet<Stirng>();
c.add("Java");
c.add("cpp");
c.add("php");
c.add("c#");
c.add("objective-c");
for(String str : c){
System.out.print(str.toUpperase()+"");
}
//CPP PHP C# JAVA OBJECTIVE-C
}
4.泛型在集合中的应用
-
泛型是java SE 5.0引入的特性,泛型的本质是参数化类型。在类、接口和方法的定义过程中,所操作的数据类型被传入的参数指定。
-
Java泛型机制广泛的应用在集合框架中。所有的集合类型都带有泛型参数,这样在创建集合时可以指定放入集合中元素的类型。Java编译器可以据此进行类型检查,这样可以减少代码在运行时出错的可能性。
-
ArrayList类的定义中,中的E为泛型参数,在创建对象时可以将类型作为参数传递,此时,类定义所有的E将被替换成传入的参数;
public class ArrayList<E>{
… … …
public boolean add(E e){…};
public E get(int index){…};
}
ArrayList<String>list = new ArrayList<String>();
List.add("One");
List.add(100);
5.集合操作-线性表
5.1 Llist
-
ArrayList 和 LinkedList
List接口是Collection的子接口,用于定义线性表数据结构。可以将List理解为存放对象的数组,只不过其元素个数可以动态的增加或减少。 -
List接口的两个常见类为ArrayList和LinkedList,分别用动态数组和链表的方式实现了List接口。
-
可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别。ArrayList适合于随机访问而LinkedList更适合于插入和删除。在性能要求不是特别苛刻的情形下可以忽略这个差别。
5.2get和set
- List除了继承Collection定义的方法外,还根据其线性表的数据结构定义了一系列方法,其中最常用的就是基于下标的get和set方法:
E get(int index)
获取集合中指定下标对应的元素,下标从0开始。
E set(int index,E elment)
将给定的元素存入给定位置,并将原位置的元素返回。
Public void testGetAndSet(){
List<String>list = new ArrayList<String>();
list.add("java"); list.add("cpp"); list.add("php");
list.add("c#"); list.add("objective-c");
//get方法遍历List
for(int i= 0 ;i<list.size();i++){
System.out.pritnln(list.get(i).toUpperCase());
}
String value = list.set(1,"c++");
System.out.println(value); //cpp
list.set(1,list.set(3,list.get(1))); //交换位置1和3上的元素
System.out.println(list); //[java,c#,php,c++,objective-c]
}
5.3 插入和删除
-
List根据下标的操作还支持插入与删除操作。
void add(int index,E element);
将给定的元素插入到指定位置,原位置及后续元素都顺序向后移动。
E remove(int index)
删除给定位置的元素,并将被删除的元素返回。
public void testInsertAndRemove(){
List<String>list = new ArrayList<String>();
list.add("java");
list.add("c#");
System.out.println(list); //[java,c#]
list.add(1,"cpp");
System.out.println(list); //[java,cpp,c#]
list.remove(2);
System.out.println(list); //[java,cpp]
}
5.4 subList
- List的subList方法用于获取子List
- 需要注意的是subList获取的List与原List占有相同的存储空间,对子List的操作会影响原List。
- List subList(int fromIndex,int toIndex);
- fromIndex和toIndex是截取子List的首尾下标(前包括,后不包括)
public void testSubList(){
List<Integer>list = new ArrayList<Integer>();
for(int i = 0; i<10;i++){
list.add(i);
}
System.out.println(list);//[0,1,2,3,4,5,6,7,8,9]
List<Integer>subList = list.subList(3,8);
System.out.println(subList);//[3,4,5,6,7]
//subList获得的List和源List占有相同的数据空间
for(int I = 0 ;i<subList.size();i++){
subList.set(i,subList.get(i)*10);
}
System.out.println(subList);//[30,40,50,60,70]
System.out.println(list);//[0,1,2,30,40,50,60,70,8,9]
list.subList(3,8).clear();//可以用于删除连续元素
System.out.println(list);
}
5.5 List转换为数组
-
List的toArray方法用于将集合转换为数组。但实际上该方法是在Collection中定义的,所以所有的集合都具备这个功能。
-
其有俩个方法:
Object[] toArray()
T[] toArray(T[] a); -
其中第二个方法是比较常用的,我们可以传入一个指定的类型的数组,该数组的元素类型应与集合的元素类型一致。返回值则是转换后的数组,该数组会保存集合中所有的元素。
public void testListToArray(){
List<String>list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
String[] strArr = list.toArray(new String[]{});
//[a,b,c]
System.out.println(Arrays.toString(strArr));
}
5.6 数组转换为List
- Arrays类中提供了一个静态方法asList,使用该方法我们可以将一个数组转换为对应的List集合。
- 其方法定义为:
static<T> List<T> asList<T… a>
- 返回的List的集合元素类型由传入的数组的元素类型决定。
- 并且要注意的是,返回的集合我们不能对其增删元素,否则会抛出异常。并且对集合的元素进行修改会影响数组对应的元素。
public void testArrayToList(){
String[] strArr = {"a","b","c"};
List<String>list = Arrays.asList(StrArr);
System.out.println(list); //[a,b,c]
//list.add("d");//会抛出UnsupporedOperationException
//java.util.Arrays$ArraysList
System.out.println(list.getClass().getName());
List<String>list1 = new ArrayList<String>();
list1.addAll(Arrays.asList(strArr));
}
5.7 List排序
- Collections.sort方法实现排序
- Collections是集合的工具类,它提供了很多便于我们操作集合的方法,其中就有用于集合排序的sort方法。
该方法定义为:
void sort(List<T> list);
该方法的作用是对给定的集合元素进行自然排序。
- Collections.sort方法实现排序
public void testSort(){
List<Integer>list = new ArrayList<Integer>();
Random r = new Random(1);
for(int I = 0 ;i<10;i++){
list.add(r.nextInt(100);
}
System.out.println(list); //[85,88,47,23,3,4,54,32,5,98]
Collections.sort(list);//从小到大排好序
System.out.println(list);
}
- Comparable
Collections的sort方法是对集合元素进行自然排序,那么两个元素对象之间就一定要有大小之分。这个大小之分是如何界定的?实际上,在使用Collections的sort排序的集合元素都必须是Comparable接口的的实现类,该接口表示其子类是可比较的,因为实现该接口必须重写抽象方法:
Int compareTo(T t);
该方法用于使当前对象与给定对象进行比较。
若当前对象大于给定对象,那么返回值应为>0的整数。
若小于给定对象,那么返回值应为<0的整数。
若两个对象相等,则应返回0。
public void testComparable(){
/* Cell实现了Comparable接口,compareTo方法逻辑为按照row值的大小排序*/
//public int compareTo(Cell o) {return this.row-o.row;}
List<Cell>cells = new ArrayList<Cell>();
cells.add(new Cell(2,3));
cells.add(new Cell(5,1));
cells.add(new Cell(3,2));
Collections.sort(cells);
System.out.println(cells); //[(2,3),(3,2),(5,1)]
}
- 一旦Java类实现了Comparable接口,其比较逻辑就已经确定;如果希望在排序的操作中临时指定比较规则,可以采用Comparator接口回调(约定)的方式。
- Comparator接口要求实现类必须重写其定义的方法:
- 该方法的返回值要求:
若o1>o2则返回值应>0;
若o1>o2则返回值应<0;
若o1==o2则返回值应为0
public void testComparator(){
List<Cell>cells = new ArrayList<Cell>();
cells.add(new Cell(2,3));
cells.add(new Cell(5,1));
cells.add(new cell(3,2));
//按照col值的大小排序
Collections.sort(cells,new Comparator<Cell>(){
public int compare(Cell o1,Cell o2){
return o1.col – o2.col;
}
});
System.out.println(cells);//[(5,1),,(3,2),(2,3)]
}
6. 队列和栈
6.1 Queue
-
队列(Queue)是常用的数据结构,可以将队列堪称特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加(offer)元素,从另一端取出(poll)元素。
-
队列遵循先进先出(FIFO first input First Output)的原则。
-
JDK中提供了Queue接口,同时使得LinkedList实现了该接口(选择LinkedList实现Queue的原因在于Queue经常要进行添加和删除的操作,而LinkedList在这方面效率较高)。
-
Queue接口中主要方法如下:
boolean offer(E e); | 将一个对象添加至队尾,如果添加成功则返回true。 |
---|---|
E poll(); | 从队首删除并返回一个元素。 |
E peek(); | 返回队首的元素(但并不删除)。 |
public void testQueue(){
Queue<String>queue = new LinkedList<String>();
queue.offer("a");
queue.offer("b");
queue.offer("c");
System.out.println(queue); //[a,b,c]
String str = queue.peek();
System.out.println(str); // a
while(queue.size()>0){
str = queue.poll();
System.out.println(str + ""); // a b c
}
}
6.2 Deque
- Deque是Queue的子接口,定义了所谓“双端队列”即从队列的两段分别可以入队(offer)和出对(poll),linkedList实现了该接口。
- 如果将Deque限制为只能从一端入队和出队,则可实现“栈”(Stack)的数据结构,对于栈而言,入栈称之为push,出栈称之为pop。
- 栈遵循先进后出(FILO First Input Last Output)的原则
pulbic void testStack(){
Deque<String>stack = new LinkedList<String>();
statck.push("a");
statck.push("b");
statck.push("c");
System.out.println(stack); //[c,b,a]
String str = stack.peek();
System.out.println(str); //c
while(stack.size()>0){
str = statck.pop();
System.out.println(str+"");//c b a }
}
7. Map接口
- Map接口定义的集合又称查找表,用于存储所谓“Key-Value”映射对。Key可以堪称是Value的索引,作为Key的对象在集合中不可以重复。
- 根据内部数据结构的不同,Map接口有多种实现类,其中常用的由内部为hash表实现的HashMap和内部为排序二叉树实现的TreeMap。
7.1 put()方法
- Map接口中定义了向Map中存放元素的put方法:
- 将Key-Value对存入Map,如果在集合中已经包含Key,则操作将替换该Key所对应的Value,返回值为该Key原来所对应的Value(如果没有则返回null)。
public void testPut(){
//向map中添加元素
employees.put(“张三”,new Emp(“张三”,25,
“男”,5000));
employees.put(“李四”,new Emp(“李四”,21,“女”,6000));
}
7.2 get()方法
Map接口中定义了从Map中获取元素的get方法:
V get(Object key)
返回参数key所对应的Value对象,如果不存在则返回null.
public void testGet(){
//从Map中使用key获取value
Emp emp = employees.get("张三");
System.out.println(emp);
}
7.3 containsKey()方法
Map接口中定义了判断某个key是否在Map中存在:
boolean containsKey(Object key);
若Map中包含给定的key则返回true,否则返回false。
public void testContainsKey(){
boolean has = employees.containsKey("李四");
System.out.println("是否有员工李四:"+has);
}
7.4 containsValue()方法
7.5 remove()方法
8. Hash表原理
8.1 hashCode方法
-
从HashMap的原理中我们可以看到,key的hashCode()方法的返回值对HashMap存储元素时会起着很重要的作用。而HashCode()方法实际上是在Object中定义的。那么应当妥善重写该方法:
-
对重写了equals方法的对象,一般要妥善的重写继承自Object类的hashCode方法(Object提供的hashCode方法将返回该对象所在内存地址的整数形式)。
-
重写hashCode方法是需要注意两点:其一,与equals方法的一致性,即equals比较返回true的两个对象其hashCode方法返回值相同;其二、hashCode返回的数值应符合hashCode算法的要求,试想如果有很多对象的hashCode方法返回值都相同,则会大大降低hash表的效率,一般情况下可以使用IDE(如Eclipse)提供的工具自动生成hashCode方法。
-
重写hashCode方法
public void hashCode(){
final int prime = 31;
int result = 1;
result = prime * result +age;
result = prime * result +((gender == null) ? 0 : gender.hashCode());
result = prime * result +((name == null) ? 0: name.hashCode());
long temp;
Temp = Double.doubleToLongBits(salary);
result = prime * result +(int) (temp ^ (temp >>>32));
return result;
}
8.2装载因子及HashMap优化
- Capacity:容量,hash表里bucket(桶)的数量,也就是散列数组大小。
- Initial capacity:初始容量,创建hash表时,初始bucket的数量,默认构建容量是16.也可以使用特定容量。
- Size:大小,当前散列表中存储数据的数量。
- Load factor:加载因子,默认值0.75(就是75%),当向散列表增加数据时如果size/capacity的值大于Load factor则发生扩展并且重新散列(rehash).
- 性能优化:加载因子较小时,散列查找性能会提高,同时也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量,减少rehash提高性能。
8.3 Map遍历
- Map提供了三种遍历方式:
遍历所有的key
遍历所有的key-value(键值对)对
遍历所有的value(不常用)
8.3.1 遍历所有key的方法:
Set <K> keyset()
该方法会将当前Map中所有的key存入一个Set集合后返回。
/** 使用keySet遍历所有监测点*/
public void testKeySet(){
Set<String> keyset = map.keyset();
for(String key: keyset){
System.out.println(“监测点:”+key);
}
}
8.3.2 使用entryset()方法
遍历所有的键值对的方法:
Set<Entry<K,V>> entrySet()
该方法会将当前Map中每一组key-value对封装为一个Entry对象并存入一个Set集合后返回。
/** 使用entrySet遍历所有检测点PM2.5最大值*/
public void testEntrySet(){
Set<Entry<String,Integer>> entrySet = map.entrySet();
for(Entry<String,Integer> entry : entrySet){
System.out.println(entry.getKey()+":"+entry.getVlaue());
}
}
8.3.3 values()
Collection values()
获取所有的value直意义不大,不常用。
9.有序Map
- LinkedHashMap实现
使用Map接口的哈希表和链表实现,具有可预知的迭代顺序。此实现与HashMap的不同之处在于:
LinkedHashMap维护着一个双向循环链表。此链表定义了迭代顺序,该迭代顺序通常就是存放元素的顺序。
需要注意的是,如果在Map中重新存入已有的key,那么key的位置不会发生改变,只是将value值替换。