Java基础--说集合框架
版权所有,转载注明出处。
1,Java中,集合是什么?为什么会出现?
根据数学的定义,集合是一个元素或多个元素的构成,即集合一个装有元素的容器。
Java中已经有数组这一装有元素的容器,为什么还要新建其它容器?废话,肯定是数组满足不了实际需求。
数组满足不了的需求:
-
-
- 动态改变容器的大小,如果加入的元素个数不确定,我们需要动态改变容器的大小。
- 数组提供的功能和性能不完善,例如查找效率低,元素之间是无序的。作为成熟的编程语言,Java需要提供更加功能更加丰富的容器。
-
鉴于此,Java创建了集合框架的相关接口和实现类。
2,集合框架的设计思想是什么?
由上所述,Java集合框架首先要可以动态改变添加和删除元素,此外还要提供更丰富的方法来处理数据。
由于集合是依赖数据元素而存在的,考虑元素的构成,我们可以将程序需要处理的数据抽象成类似于关系数据库的形式,每一行是一个数据,并且有索引(主键),我们将数据分为<索引:数据>这样的键值对形式,Java中用Map接口就是基于此产生。
考虑数据为单一元素时(基本数据类型或引用类型),虽然仍可以用Map存储(可以用一个自增值作为键)但意义不大。为了适应这种情况Collection接口被创造出来。
以上就是Java集合框架顶层接口Map和Collection的设计初衷。
3,具体集合类有哪些?它们特点是什么?
3.1、首先考虑简单的Collection接口,对于单个元素存储来说,根据单个元素属性来创建Collection的子接口或者实现类。这些属性有是否要存储相同的,存储是否是有序的。在Java中我们根据是否要存储相同元素设计了Collection常用的两个子接口List和Set.相同和不相同的判断默认判断标准是e1.equals(e2)。
-
- List:可以存储重复元素
- Set:只存不同元素
List:实现List接口常见的类有:
-
-
- ArrayList:底层是可变数组数据结构实现,因此查询快(地址连续,跳转少),增删慢(地址改变),但线程不安全,多线程用时注意。
- Vector:线程安全的ArrayList,每个方法都被同步了,因此使用效率低,实际中使用较少。
- LinkedList:底层用双向链表实现,因此查询慢(地址跳转多),增删快。
-
使用原则:不考虑性能,常用ArrayList。当考虑性能时,如果需要查询操作较多用ArrayList,增删操作较多则用LinkedList
Set:实现Set接口常见的类有,Set用默认用元素的hashCode()方法保证元素的唯一性。
-
-
- HashSet:底层用哈希表实现,无序,加入顺序和存储顺序没有必然联系。
- TreeSet:底层用红黑树(自平衡的二叉树)。使用元素的自然顺序,e1.equals(e2)比较作为大小判断标准。
-
使用原则:根据是否需要对添加的元素进行划分。需要用TreeSet,否则用HashSet
3.2 Map接口,Map接口较为简单,不过需要强调的是Map进行比较等操作都是针对键来说的。常用的实现类:
-
-
- HashMap:无序
- TreeMap:有序
- HashTable:HashMap的线程安全实现,不允许NULL,作为键值。
-
4,具体集合类常用方法?注意事项?
对于集合类来讲,添加,查找,遍历,删除是必须的。其中添加,查找,遍历是基础操作,其它都是依赖于这些操作进行的。
4.1Collection:
添加:boolean add(E e)
查找:boolean contains(Object o)是否包含
遍历:用迭代器接口Iterator<E> iterator()
使用样例:
Iterator it = c.iterator(); while(it.hasNext()){ it.next(); }
以ArrayList为例:
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class ArrayListDemo { public static void main(String[] args) { Collection<String> al = new ArrayList<String>(); al.add("a");//添加 al.add("b"); al.add("c"); al.add("d"); //查询是否含有 System.out.println(al.contains("a")); //遍历 Iterator<String> i = al.iterator(); while(i.hasNext()){ String tmp = i.next(); System.out.println(tmp); } for(String str:al){System.out.println(str);} } }
类特有的方法
ArrayList,由于ArrayList是用可变数组实现的因此会有关索引的操作。
ArrayList:扩容如果初始不指明ArrayList大小,则初始化为10
当需要扩容时,可以指定扩容大小,默认增长为原来的1.5倍-1(Vector扩容为原来的一倍。)
ArrayList()是顺序添加元素。先进位置越靠前。
E get(int index):返回索引位置的的数据
E set(int index, E element):修改索引位置的元素
E remove(int index)
boolean remove(Object o)
LinkedList,采用双向链表实现,因此具备一个反向的迭代器。
ListIterator<String> i = lk.listIterator(lk.size()); while(i.hasPrevious()){ String tmp = i.previous(); System.out.println(tmp); } for(String str:lk){System.out.println(str);} /** * d * c * b * a * a * b * c * d */
Set:
-
- 保证元素的唯一性机制
查看Set的add方法源码:boolean add(E e);是一个抽象方法,
因此定位到HashSet的add()方法
//HashSet的add源码 map是一个HashMap对象 public boolean add(E e) { return map.put(e, PRESENT)==null; }
发现HashSet是使用HashMap来保证元素的唯一性。追踪的HashMap的put()方法源码:
int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null;
添加元素时先计算待添加元素的哈希值,然后从table中查找,如果查找到,则e不为空,不进入循环,则原来的元素不动,否则进入循环再利用equals() 方法判断是否和其它元素相同。
综上Set用来保证元素唯一性的机制是:
比较hashCode(),根据hashCode()查找元素:
查找:
yes-->原来位置的元素不发生改变
no:
用equals()和其他元素相比
相同 原来位置的元素不发生改变
不同 加入新元素
同理追踪TreeSet的源码可以得到它也是由TreeMap来保证有序的。
-
- HashSet的使用
和ArrayList类似没有很特殊的方法,如果需要加入的元素的不同一般使用HashSet
-
- TreeSet的使用
TreeSet可以保证元素以自然顺序(equals())排列,默认递增。
-
- 使用Set注意事项
保证引用类型元素唯一:
其实,Set使用元素的hashCode()返回值和equals()保证唯一,用默认的比较器保证有序,这在元素是基本数据类型时没有问题的,但当数据元素是引用类型时,由于相同对象的不同引用可能在现实中代表同一个对象实例。例如一下情景:
Student类
//假设业务要求姓名和年龄相同代表同一个学生 public class Student(){ String name; int age; public Student(String name, int age){ this.name = name; this.age = age; } }
HashSetDemo主类
import java.util.HashSet; import java.util.Set; public class HashSetDemo { public static void main(String[] args) { Set<Student> s = new HashSet<Student>(); Student s1 = new Student("小明",16); Student s2 = new Student("小明",16); s.add(s1); s.add(s2); for(Student st:s){System.out.println(st);} } } /* * 输出: * collection.Student@4b0bc3c9 * collection.Student@5f0ee5b8 * */
可以看到HashSet将相同的一个人加了两次,因此为了这种情况,在数据元素是自定义类时,我们通常需要自定义hashCode()方法和equals()方法。
就需要改写方法元素的hashCode()和equals()方法。在Eclipse中可以自动生成。Source-->Generate hashCode()和equals()
修改后的Student类
public class Student { @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.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; Student other = (Student) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } String name; int age; public Student(String name, int age){ this.name = name; this.age = age; } }
TreeSet保证引用类型有序
TreeSet构造方法
TreeSet(Comparator<? super E> comparator).Comparator是一个比较器接口,我们新建TreeSet时不声明比较器,这时TreeSet按照元素的自然顺序(equals())上升排序,当我们自定义Comparator类后TreeSet就可以按照我们业务要求来写比较器。下面是一个针对学生类的比较器
业务规定:按年龄从大到小排序
比较器类
public class MyComparator implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { if(o1.age>o2.age){ return -1; } if(o1.age<o2.age){ return 1; } if(o1.equals(o2)&&o1.hashCode()==o2.hashCode()){ return 0; } return 1; } }
之所以比较hashCode和equals方法是因为TreeSet加入元素时如果如果返回为0会认为两个元素相同,只添加一个(原来的值)。
主类调用
import java.util.Set; import java.util.TreeSet; public class TreeSetDemo { public static void main(String[] args) { Set<Student> s = new TreeSet<Student>(new MyComparator()); Student s1 = new Student("小明",16); Student s2 = new Student("小lizi",19); Student s3 = new Student("小lizi",20); Student s4 = new Student("小lizie",20); s.add(s1); s.add(s2); s.add(s3); s.add(s4); for(Student st:s){System.out.println(st.toString());} } /* * (Student [name=小lizi, age=20] * Student [name=小lizie, age=20] * Student [name=小lizi, age=19] * Student [name=小明, age=16] */ }
4.2 Map
Map是针对元素为键值对形式数据的容器,由于Set数据的加入依赖Map的实现类HashMap和TreeMao,故Set使用事项的描述也适用于Map。即在Map加入引用数据类型作为键值时,也应该重写键值对象的hashCode()和equals()方法,同时TreeMap再加入元素时,需要自定义比较器。
Map和Collection使用上形式上最大的不同是遍历操作:
Set<Map.Entry<K,V>> entrySet()
使用规则:
Map<String,String> map = new HashMap<String,String>(); map.put("a", "a"); map.put("b", "b"); map.put("c", "c"); map.put("d", "d"); Set<Map.Entry<String, String>> myset = map.entrySet(); for(Map.Entry<String, String> me:myset){ System.out.println(me.getKey()); System.out.println(me.getValue()); }
此外Map提供获取所有的键集,值集等操作,可查阅API使用,较为简单。
5,总结集合框架
5.1结构
Collection
|List
|ArrayList:一般使用,数组实现,查询快,增删慢,线程不安全
|Vector:较为少用,线程安全,但效率较低
|LinkedList:双向链表实现,查询慢,增删快,线程不安全
|Set
|HashSet:HashMap
|TreeSet:红黑二叉树实现
Map:
|HashMap 无序
|TreeMap 有序
5.2 泛型
List<String> a = new ArrayList<String>();//a中存储的数据类型在编译器就确定下来
List a = new ArrayList();//a中存储的数据类型运行时才知道
ps.