这是对集合总结的类和常用方法,希望对大家有帮助。
--------------------华丽的分割线----------------------------------
2.1. List
List接口是Collection的子接口,用于定义线性表数据结构;可以将List理解为存放对象的数组,只不过其元素个数可以动态的增加或减少。并且List是可重复集,这个我们在以前的章节已经描述。
2.1.1. ArrayList和LinkedList
List接口的两个常见实现类为ArrayList和LinkedList,分别用动态数组和链表的方式实现了List接口。
可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList更适合于随机访问而LinkedList更适合于插入和删除;在性能要求不是特别苛刻的情形下可以忽略这个差别。
2.1.2. get与set方法
List除了继承Collection定义的方法外,还根据其线性表的数据结构定义了一系列方法,其中最常用的就是基于下标的get和set方法。
· E get(int index):获取集合中指定下标对应的元素,下标从0开始。
· E set(int index, E elment):将给定的元素存入给定位置,并将原位置的元素返回。
例如:
<span style="font-size:14px;color:#000000;">1. List<String> list = new ArrayList<String>();
2. list.add("java");
3. list.add("cpp");
4. list.add("php");
5. list.add("c#");
6. list.add("objective-c");
7. // get方法遍历List
8. for (int i = 0; i < list.size(); i++) {
9. System.out.println(list.get(i));
10. }
11. String value = list.set(1, "c++");
12. System.out.println(value); // cpp
13. System.out.println(list); // [java, c++, php, c#, objective-c]
14. // 交换位置1和3上的元素
15. list.set(1, list.set(3, list.get(1)));
16. System.out.println(list);
17. // [java, c#, php, c++, objective-c]
</span>
2.1.3. 插入和删除
List根据下标的操作还支持插入与删除操作:
void add(int index,E element):
将给定的元素插入到指定位置,原位置及后续元素都顺序向后移动。
E remove(int index):
删除给定位置的元素,并将被删除的元素返回。
例如:
<span style="font-size:14px;color:#000000;">1. List<String> list = new ArrayList<String>();
2. list.add("java");
3. list.add("c#");
4. System.out.println(list); // [java, c#]
5.
6. list.add(1, "cpp");
7. System.out.println(list); // [java, cpp, c#]
8.
9. list.remove(2);
10. System.out.println(list); // [java, cpp]
</span>
2.1.4. subList方法
List的subList方法用于获取子List。
需要注意的是,subList获取的List与原List占有相同的存储空间,对子List的操作会影响的原List。
List<E> subList(int fromIndex, int toIndex);
fromIndex和toIndex是截取子List的首尾下标(前包括,后不包括) 。
例如:
<span style="font-size:14px;color:#000000;">1. List<Integer> list = new ArrayList<Integer>();
2. for (int i = 0; i < 10; i++) {
3. list.add(i);
4. }
5. System.out.println(list); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6. List<Integer> subList = list.subList(3, 8);
7. System.out.println(subList); // [3, 4, 5, 6, 7]
8. // subList获得的List和源List占有相同的数据空间
9. for (int i = 0; i < subList.size(); i++) {
10. subList.set(i, subList.get(i) * 10);
11. }
12. System.out.println(subList); // [30, 40, 50, 60, 70]
13. System.out.println(list); // [0, 1, 2, 30, 40, 50, 60, 70, 8, 9]
14. // 可以用于删除连续元素list.subList(3, 8).clear();
15. System.out.println(list);
</span>
2.1.5. List转换为数组
List的toArray方法用于将集合转换为数组。但实际上该方法是在Collection中定义的,所以所有的集合都具备这个功能。
其有两个方法:
Object[] toArray()
<T>T[] toArray(T[] a)
其中第二个方法是比较常用的,我们可以传入一个指定类型的数组,该数组的元素类型应与集合的元素类型一致。返回值则是转换后的数组,该数组会保存集合中所有的元素。
例如:
<span style="font-size:14px;color:#000000;">1. List<String> list = new ArrayList<String>();
2. list.add("a");
3. list.add("b");
4. list.add("c");
5.
6. //通常我们传入的数组不需要给定长度
7. String[] strArr = list.toArray(new String[] {}); System.out.println(Arrays.toString(strArr)); // [a, b, c]
</span>
2.1.6. 数组转换为List
Arrays类中提供了一个静态方法asList,使用该方法我们可以将一个数组转换为对应的List集合。
其方法定义为:
static <T>List<T> asList<T… a>
返回的List的集合元素类型由传入的数组的元素类型决定。
需要注意的是,返回的集合我们不能对其增删元素,否则会抛出异常。并且对集合的元素进行的修改会影响数组对应的元素。
例如:
<span style="font-size:14px;color:#000000;">1. String[] strArr = { "a", "b", "c" };
2. List<String> list = Arrays.asList(strArr);
3. System.out.println(list); // [a, b, c]
4. // list.add("d"); // 会抛出UnsupportedOperationException
5.
6. // java.util.Arrays$ArrayList
7. System.out.println(list.getClass().getName());
8. List<String> list1 = new ArrayList<String>();
9. list1.addAll(Arrays.asList(strArr));
</span>
2.2. List排序
2.2.1.Collections.sort方法实现排序
Collections是集合的工具类,它提供了很多便于我们操作集合的方法,其中就有用于集合排序的sort方法。该方法的定义为:
void sort(List<T> list)
其作用是对集合元素进行自然排序(按照元素的由小至大的顺序)
例如:
<span style="font-size:14px;color:#000000;">1. List<Integer> list = new ArrayList<Integer>();
2. Random r = new Random(1);
3. for (int i = 0; i < 10; i++) {
4. list.add(r.nextInt(100));
5. }
6. System.out.println(list); // [85, 88, 47, 13, 54, 4, 34, 6, 78, 48]
7. Collections.sort(list);
8. System.out.println(list); // [4, 6, 13, 34, 47, 48, 54, 78, 85, 88]
</span>
2.2.2. Comparable
通过上一节我们知道了如何对集合元素进行自然排序,但是要想对元素进行自然排序那么就必须要有一个必要条件,就是元素的大小。集合中存入的都是引用类型,是以对象的形式存在于内存中,那么对象是如何进行的大小比较呢?实际上,若想对某个集合的元素进行自然排序,该集合的元素有一个要求,就是这些元素必须是Comparable的子类。
Comparable是一个接口,用于定义其子类是可以比较的。因为该接口有一个抽象方法:
int compareTo(T t)
所有子类都需要重写该方法来定义对象间的比较规则。该方法要求返回一个整数,这个整数不关心具体的值,而是关注取值范围。
当返回值>0时,表示当前对象比参数给定的对象大。
当返回值<0时,表示当前对象比参数给定的对象小。
当返回值=0时,表示当前对象和参数给定的对象相等。
例如:
<span style="font-size:14px;color:#000000;">1. Class Cell implements Comparable<Cell>{
2. int row;
3. int col;
4.
5. public Cell(int row,int col){
6. this.row = row;
7. this.col = col;
8. }
9.
10. public int compareTo(Cell c){
11. //根据row比较大小
12. return this.row - c.row;
13. }
14. }
</span>
那么Collections的sort在进行排序时就会根据集合中元素的compareTo方法的返回值来判断大小从而进行自然排序。
<span style="font-size:14px;color:#000000;">1. // Cell实现了Comparable接口,CompareTo方法逻辑为按照row值的大小排序
2. List<Cell> cells = new ArrayList<Cell>();
3. cells.add(new Cell(2, 3));
4. cells.add(new Cell(5, 1));
5. cells.add(new Cell(3, 2));
6. Collections.sort(cells);
7. System.out.println(cells); // [(2,3), (3,2), (5,1)]
</span>
2.2.3. comparator
一旦Java类实现了Comparable,其比较逻辑就已经确定;如果希望在排序的操作中临时指定比较规则,可以采用Comparator接口回调的方式。
该接口要求实现类必须重写其定义的方法:
int compare(T o1,T o2)
该方法的返回值要求,若o1>o2则返回值应>0,若o1<o2则返回值应<0,若o1==o2则返回值应为0
例如:
<span style="font-size:14px;color:#000000;">1. List<Cell> cells = new ArrayList<Cell>();
2. cells.add(new Cell(2, 3));
3. cells.add(new Cell(5, 1));
4. cells.add(new Cell(3, 2));
5. // 按照col值的大小排序
6. Collections.sort(cells, new Comparator<Cell>() {
7. @Override
8. public int compare(Cell o1, Cell o2) {
9. return o1.col - o2.col;}
10. });
11. System.out.println(cells); // [(5,1), (3,2), (2,3)]
</span>
3.1. Map接口
3.1.1. Map 接口
java提供了一组可以以键值对(key-value)的形式存储数据的数据结构,这种数据结构成为Map。我们可以把Map看成一个多行两列的表格,其中第一列存放key,第二列存放value。
而每一行就相当于一组key-value对,表示一组数据。
Map对存入的元素有一个要求,就是key不能重复,所谓不能重复指的是在Map中不能包含两个equals为true的key。
Map对于key,value的类型没有严格要求,只要是引用类型均可。但是为了保证在使用时不会造成数据混乱,通常我们会使用泛型去约束key与value的类型。
3.1.2. put方法
既然我们知道了Map在保存数据时实际上是存入了两部分信息的,key与value。那么我们来看看如何向Map中存入数据。
Map提供了一个方法:
V put(K k,V v)
该方法的作用是将key-value对存入Map中,因为Map中不允许出现重复的key,所以若当次存入的key已经在Map中存在,则是替换value操作,而返回值则为被替换的元素。若此key不存在,那么返回值为null。
3.1.3. get方法
我们学会了如何向Map中存入数据,那么我们再来看看如何获取数据。Map中获取数据的方式是给定Key获取对应的Value。
Map提供了一个方法:
V get(Object key)
该方法的作用就是根据给定的key去查找Map中对应的value并返回,若当前Map中不包含给定的key,那么返回值为null。
3.1.4. containsKey方法
Map中的containsKey方法用于检测当前Map中是否包含给定的key。其方法定义如下:
boolean containsKey(Object key)
若当前Map中包含给定的key(这里检查是否包含是根据key的equals比较结果为依据的。)则返回true。
3.2. HashMap
3.2.1. hash表原理
HashMap是Map的一个常用的子类实现。其实使用散列算法实现的。
HashMap内部维护着一个散列数组(就是一个存放元素的数组),我们称其为散列桶,而当我们向HashMap中存入一组键值对时,HashMap首先获取key这个对象的hashcode()方法的返回值,然后使用该值进行一个散列算法,得出一个数字,这个数字就是这组键值对要存入散列数组中的下标位置。
那么得知了下标位置后,HashMap还会查看散列数组当前位置是否包含该元素。(这里要注意的是,散列数组中每个元素并非是直接存储键值对的,而是存入了一个链表,这个链表中的每个节点才是真实保存这组键值对的。)检查是否包含该元素时根据当前要存入的key在当前散列数组对应位置中的链表里是否已经包含这个key,若不包含则将这组键值对存入链表,否则就替换value。
那么在获取元素时,HashMap同样先根据key的hashcode值进行散列算法,找到它在散列数组中的位置,然后遍历该位置的链表,找到该key所对应的value之后返回。
看到这里可能有个疑问,链表中应该只能存入一个元素,那么HashMap是如何将key-value存入链表的某个节点的呢?实际上,HashMap会将每组键值对封装为一个Entry的实例,然后将该实例存入链表。
3.2.2. hashcode方法
HashMap的存取是依赖于key的hashcode方法的返回值的,而hashcode方法实际上是在Object中定义的。其定义如下:
int hashCode()
重写一个类的hashcode()方法有以下注意事项:
1、若一个类重写了equals方法,那么就应当重写hashcode()方法。
2、若两个对象的equals方法比较为true,那么它们应当具有相同的hashcode值。
3、对于同一个对象而言,在内容没有发生改变的情况下,多次调用hashCode()方法应当总是返回相同的值。
4、对于两个对象equals比较为false的,并不要求其hashcode值一定不同,但是应尽量保证不同,这样可以提高散列表性能。
3.2.3. 装载因子及HashMap优化
在散列表中有一下名词需要了解:
· Capacity:容量, hash表里bucket(桶)的数量, 也就是散列数组大小.
· Initial capacity:初始容量, 创建hash表的时 初始bucket的数量, 默认构建容量是16. 也可以使用特定容量.
· Size : 大小, 当前散列表中存储数据的数量.
· Load factor:加载因子, 默认值0.75(就是75%), 向散列表增加数据时如果 size/capacity 的值大于Load factor则发生扩容并且重新散列(rehash).
那么当加载因子较小时候散列查找性能会提高,同时也浪费了散列桶空间容量. 0.75是性能和空间相对平衡结果.在创建散列表时候指定合理容量, 从而可以减少rehash提高性能。
3.3. 有序Map
3.3.1. LinkedHashMap实现有序的Map
Map 接口的哈希表和链表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,LinkedHashMap维护着一个双向循环链表。此链表定义了迭代顺序,该迭代顺序通常就是将存放元素的顺序。
需要注意的是,如果在Map中重新存入以有的key,那么key的位置会不会发生改变,只是将value值替换。