咱们知道,计算机的优点在于处理大量的数据,在编程开发中,为处理大量的数据,必须具有相应的存储结构,以前学习的数组能够用来存储并处理大量类型相同的数据,可是经过上面的课后练习,会发现数组在应用中的限制:数组长度一旦肯定,就没法更改;除非采用创建新数组,再将原数组内容拷贝过来;数组中只能存放指定类型的数据,操做不方便。在实际开发中,为了操做方便,JDK中提供了List集合。java
List集合与数组的用途很是类似,都是用来存储大量数据的,不一样处有两点:web
1. 数组长度在使用前必须肯定,一旦肯定不能改变。而List集合长度可变,无需定义。编程
2. 数组中必须存放同一类型的数据,List集合中能够存放不一样类型的数据。数组
List集合是Java集合框架中的一种,另外两种集合Set和Map会在下面介绍。List集合在JDK中被封装称为接口,针对List接口,有若干种实现,经常使用的有三个子类,即ArrayList、Vector和LinkedList。这三个类的功能与用法相同,但内部实现方式不一样。下面以ArrayList为例介绍集合的经常使用操做,Vector和LinkedList的使用方法与ArrayList相似。安全
数组与List集合的常规操做相似,下面经过代码对比二者的用法:框架
代码演示:数组的基本操做性能
public class ArrayDemo {学习 public static void main(String[] args) {this String[] array = new String[3];spa for (int i = 0; i < 3; i++) { array[i] = "Hello"; } String a = array[0]; } } |
代码演示:List集合的基本操做
import java.util.ArrayList; ① public class ListDemo { public static void main(String[] args) { ArrayList list = new ArrayList(); ② for (int i = 0; i < 3; i++) { list.add("Hello"); ③ } String a = (String)list.get(0); ④ } } |
代码解析:
① 集合框架在java.util包中,使用前必须使用import语句引入对应的类。
② List集合的定义时不须要指定大小,也不用指定集合中保存的数据类型。
③ 向List集合中添加数据时不须要制定下标,List集合会自动生成下标。
④ 获取List集合的元素时使用get方法并传入下标,而后强制类型转换为实际类型。
代码演示:使用集合记录学员姓名
public static void main(String[] args) { System.out.println("请输入班级学员姓名,输入OVER结束"); java.util.Scanner scanner = new java.util.Scanner(System.in); ArrayList list = new ArrayList(); do { String name = scanner.next(); if (name.equalsIgnoreCase("OVER")) break; list.add(name); } while (true); System.out.println(list); ① } |
代码解析:
① List集合重写了toString方法,能够将集合中的元素依次输出。
下表列出了List集合的经常使用方法:
返回类型 |
方法名称 |
说明 |
boolean |
add(Object obj) |
加入元素,返回是否添加成功 |
boolean |
clear() |
清除集合中的元素 |
boolean |
contains(Object obj) |
查找集合中是否存在传入的元素 |
Object |
get(int index) |
获取指定位置的元素 |
boolean |
isEmpty() |
判断集合是否为空 |
Object |
remove(int index) |
删除制定位置的元素,并返回该元素 |
int |
size() |
获取集合大小 |
Object[] |
toArray() |
将集合转换成一个数组 |
表: List集合的经常使用方法
下面经过实例演示各个方法的用途:
代码演示:List集合的经常使用方法
import java.util.ArrayList; public class Demo { public static java.util.Scanner scanner = new java.util.Scanner(System.in); public static void main(String[] args) { ArrayList listA = new ArrayList(); ArrayList listB = new ArrayList(); System.out.println("请输入A班学员姓名,输入OVER结束"); inputName(listA); System.out.println("请输入B班学员姓名,输入OVER结束"); inputName(listB); //合并集合listA与listB listA.addAll(listB); System.out.println("请输入要查找的学员姓名"); String name = scanner.next(); int pos = listA.indexOf(name); if (pos==-1) { System.out.println("没有找到"); } else { System.out.println("找到了,位置是:" + pos); } System.out.println("请输入要删除的学员姓名"); String delName = scanner.next(); if (listA.remove(delName)) { System.out.println("删除成功!"); } else { System.out.println("没有该学员"); } } public static void inputName(ArrayList list) { do { String name = scanner.next(); if (name.equalsIgnoreCase("OVER")) break; list.add(name); } while (true); } } |
使用List集合保存对象时,主要注意如下几点:
1. 集合中保存的是对象的引用,观察如下代码:
代码演示:使用集合保存对象
import java.util.ArrayList; class Student { String name; int age; public Student(String name, int age) { this.name = name; this.age = age; } public String toString() { return name + "/" + age; } } public class Demo { public static void main(String[] args) { ArrayList list = new ArrayList(); Student stu = new Student("Tom" , 10); for (int i = 0; i < 3; i++) { stu.age = 10 + i; list.add(stu); } System.out.println(list); } } |
上面代码的原意是在集合中保存三个Student对象,age分别为十、十一、12,但实际输出的age值均为12。这是由于list集合中保存的是stu对象的引用,而在循环中stu的引用并无变化,因此循环结束后集合中的三个元素都指向stu对象,age的值天然也是最后的12。将代码“Student stu = new Student("Tom" , 10);”放入循环内能够解决这一问题。
2. 使用remove、contains、indexOf等方法时,应该重写类的equals方法,观察如下代码:
代码演示:未重写equals方法的代码
//省略了Student类的定义 public class Demo { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(new Student("Tom" , 11)); list.add(new Student("Jerry" , 22)); list.add(new Student("Alice" , 33)); System.out.println(list.contains(new Student("Tom" , 11))); System.out.println(list.indexOf(new Student("Jerry" , 22))); System.out.println(list.remove(new Student("Alice" , 33))?"成功":"无此项"); } } |
在上例中,咱们但愿判断学员Tom是否存在,查找学员Jerry,删除学员Alice,可是输出的结果倒是不存在,找不到,删不掉。这是由于List集合会调用元素的equals方法来判断对象是否相等,而Student类没有重写equals方法,默认是按引用地址比较,而每一个学员对象的地址又不相同,因此出现这个现象。经过给Student类添加equals方法能够解决这个问题:
代码演示:重写equals方法后的Student类
class Student { String name; int age; public Student(String name, int age) { this.name = name; this.age = age; } public boolean equals(Object obj) { if (obj == null) return false; if (!(obj instanceof Student)) return false; Student stu = (Student) obj; return stu.name.equals(this.name) && stu.age == this.age; } } |
咱们说过,ArrayList、Vector与LinkedList的使用方法相同,内部实现方式不一样。而内部实现方式的不一样又决定了三种集合的适用范围,了解三种集合的内部实现,才能正确的选择使用类型。
? ArrayList与Vector比较
ArrayList与Vector的内部实现相似,Vector设计为线程安全,ArrayList设计为非线程安全。为了保证线程安全,Vector在性能方面稍逊于ArrayList,目前咱们编写的都是单线程应用程序,应选择使用ArrayList。
? ArrayList与LinkedList
ArrayList与LinkedList均设计为非线程安全,ArrayList内部采用数组实现(与Vector相同),LinkedList内部采用链表结构实现。
ArrayList采用数组保存元素,意味着当大量添加元素,数组空间不足时,依然须要经过新建数组、内存复制的方式来增长容量,效率较低;而当进行对数组进行插入、删除操做时,又会进行循环移位操做,效率也较低;只有进行按下标查询时(get方法),使用数组效率很高。
LinkedList采用链表保存元素,在添加元素时只须要进行一次简单的内存分配便可,效率较高;进行插入、删除操做时,只需对链表中相邻的元素进行修改便可,效率也很高;但进行按下标查询时,须要对链表进行遍历,效率较低。下图演示了链表结构的特性:
图: 链表结构,每一个元素引用后面的元素
图: 向链表中插入元素,只需修改两处引用
图: 删除链表中的元素,也只须要修改两处引用
能够总结出ArrayList在进行数据的新增、插入、删除时效率较低,按下标对数据进行查找时效率较高;LinkedList正好相反。通常来讲ArrayList保存常常进行查询操做的集合,LinkedList适用于保存经常进行修改操做的集合。
1. List集合与数组的区别。
2. List集合实际上包含了3个经常使用的集合类,即ArrayList、Vector和LinkedList。
3. List集合的经常使用操做。
4. ArrayList采用数组保存元素,意味着当大量添加元素,数组空间不足时,依然须要经过新建数组、内存复制的方式来增长容量,效率较低;而当进行对数组进行插入、删除操做时,又会进行循环移位操做,效率也较低;只有进行按下标查询时(get方法),使用数组效率很高。
5. ArrayList与Vector的内部实现相似,Vector设计为线程安全,ArrayList设计为非线程安全。为了保证线程安全,Vector在性能方面稍逊于ArrayList,目前咱们编写的都是单线程应用程序,应选择使用ArrayList。
6. ArrayList与LinkedList均设计为非线程安全,ArrayList内部采用数组实现(与Vector相同),LinkedList内部采用链表结构实现。
7. LinkedList采用链表保存元素,在添加元素时只须要进行一次简单的内存分配便可,效率较高;进行插入、删除操做时,只需对链表中相邻的元素进行修改便可,效率也很高;但进行按下标查询时,须要对链表进行遍历,效率较低。
在上面讲的List集合中,可用经过List集合提供的各类方法来对其中的元素进行操做,从而能够方便用户操做,可是若是要从List集合中获取一个特定的对象,操做是比较繁琐的。
在类Person中有cardId和name两个属性,分别表明编号和姓名,建立两个Person对象并存储到ArrayList集合中 ,若是要从集合中获取指定的对象,则必需要经过迭代整个集合来得到,以下所示:
代码演示:Person类
public class Person { String cardId; String name; public Person(String cardId, String name) { this.cardId = cardId; this.name = name; } } |
代码演示:从ArrayList中获取特定的对象
public class ArrayListTest { public static void main(String[] args) { Person personA = new Person("001", "Tom"); Person personB = new Person("002", "Jack"); ArrayList list = new ArrayList(); list.add(personA); list.add(personB); for (int i = 0; i < list.size(); i++) { Person person = (Person) list.get(i); if (person.cardId.equals("002")) { System.out.println(person.name); } } } } |
从上面的示例中,咱们看到从list集合中获取一个对象的繁琐,有没有简单的方法呢?在JDK中专门提供了Map集合来存储上面这种一对一映射关系的对象。
Map集合用于保存具备映射关系的数据,即以键值对(key->value)的方式来存储数据。所以在Map集合内部有两个集合,一个集合用于保存Map中的key(键),一个集合用于保存Map中的value(值),其中key和value能够是任意数据类型数据。
图: Map集合
Map集合中的经常使用类有Hashtable和HashMap,两个类的功能和用法类似,下面以HashMap为例介绍Map集合的用法。
代码演示:Map集合使用
public class MapTest { public static void main(String[] args) { HashMap map = new HashMap(); ① map.put("001", "Tom"); ② map.put("002", "Jack"); String name = (String) map.get("002"); ③ System.out.println(name); } } |
代码解析:
① 建立HashMap对象。
② 利用HashMap中的put方法将键值对形式的对象进行存储,put方法中的第一个参数为映射关系中key的值,put方法的第二个参数为映射关系中value的值。
③ 利用HashMap的get方法获取key对应的value,而后强制类型转换为实际类型。get中的参数为key,返回值为key对应的value。
下表列出了HashMap中经常使用的方法:
返回类型 |
方法名称 |
做用 |
Object |
put(Object key,Object value) |
加入元素,返回与此key关联的原有的value,不存在则返回null |
void |
clear() |
从集合中移除全部的元素 |
boolean |
containsKey(Object key) |
根据key从集合中判断key是否存在 |
boolean |
containsValue(Object value) |
根据value从集合中判断value是否存在 |
Object |
get(Object key) |
根据key返回key对应的值 |
Set |
keySet() |
返回Map集合中包含的键集合 |
Object |
remove(Object key) |
从集合中删除key对应的元素,返回与key对应的原有value,不存在则返回null |
int |
size() |
返回集合中的元素的数量 |
表: HashMap经常使用方法
Map集合的综合示例:
代码演示:Map集合综合演示
import java.util.HashMap; import java.util.Scanner; public class TestMap { public static void main(String[] args) { HashMap map = new HashMap(); Scanner scanner = new Scanner(System.in); for (int i = 0; i < 5; i++) { System.out.println("请输入身份证号:"); String id = scanner.next(); System.out.println("请输入姓名:"); String name = scanner.next(); map.put(id, name); ① } int size = map.size(); ② System.out.println("数据输入完毕!共" + size + "条数据!\n---------------------------------"); String answer = "no"; do { System.out.println("请输入你要查找的用户的身份证号:"); String id = scanner.next(); if (map.containsKey(id)) { ③ String name = (String) map.get(id); ④ System.out.println("您查找的用户姓名为:" + name); } else { System.out.println("您查找的用户不存在!"); } System.out.println("您还要继续查找吗?(yes/no)"); answer = scanner.next(); } while ("yes".equalsIgnoreCase(answer)); System.out.println("请输入要删除的用户的身份证号:"); String id = scanner.next(); if (map.containsKey(id)) { String name = (String) map.remove(id); ⑤ System.out.println("用户" + name + "删除成功!"); } else { System.out.println("您要删除的用户不存在!"); } System.out.println("要格式化系统吗?(yes/no)"); String format = scanner.next(); if ("yes".equalsIgnoreCase(format)) { map.clear(); ⑥ System.out.println("系统格式化完毕!当前系统中数据为" + map.size() + "条"); } System.out.println("程序运行结束!"); } } |
代码解析:
① 使用put方法将身份证号和姓名存入Map集合中。
② 使用size方法得到集合中的映射关系条数。
③ 使用containsKey方法判断集合是否存在与key对应的映射关系。
④ 使用get方法得到身份证号对应的姓名。
⑤ 使用remove方法删除身份证号对应的用户,返回身份证号对应的姓名。
⑥ 使用clear方法删除Map集合中全部的映射关系。
来看下面的一个示例:
代码演示:Map集合中重复key
import java.util.HashMap; public class DemoMap { public static void main(String[] args) { HashMap map = new HashMap(); map.put("001", "小美"); map.put("002", "阿聪"); ① map.put("002", "小莉"); ② String name = (String) map.get("002"); System.out.println(name); } } |
注意①和②处的代码,在向集合中添加值的时候,使用了重复的key,可是value不一样,在下面得到key“002”的value为多少呢?程序运行的结果是“小莉”。从结果能够中能够知道,Map集合中的key不能是重复的,若是重复,那么后面添加的映射关系会覆盖前面的映射关系。致使这样状况的出现主要是由于Map集合中的key的维护是依靠Set集合(立刻会学习到)完成的。
HashMap和Hashtable的操做是相同的,他们的区别以下:
? Hashtable是线程安全的,HashMap是非线程安全的。全部HashMap比Hashtable的性能更高。
? Hashtable不容许使用使用null值做为key或value,可是HashMap是能够的。
Set集合和List集合的不少的用法是相同的。可是Set集合中的元素是无序的,元素也是不能重复的。Set集合中经常使用类为HashSet。
HashSet类中经常使用的方法以下:
返回类型 |
方法名称 |
做用 |
boolean |
add(Object obj) |
加入元素 |
void |
clear() |
移除Set集合中全部元素 |
boolean |
contains(Object obj) |
判断Set集合中是否包含指定元素 |
boolean |
isEmpty() |
判断Set集合是否为空 |
Iterator |
iterator() |
返回Set集合中对元素迭代的迭代器 |
boolean |
remove(Object obj) |
从集合中删除元素 |
Int |
size() |
返回集合中的元素数量 |
表: HashSet类经常使用方法
经过上面的表,能够清楚的看到Set集合的用法和List集合是类似的,可是须要注意Set集合的迭代和List集合是不一样的,List的集合的迭代能够经过for循环得到索引来进行,可是Set集合的迭代必需要经过迭代器进行。
代码演示:Set集合的迭代
import java.util.HashSet; import java.util.Iterator; public class SetIterator { public static void main(String[] args) { HashSet set = new HashSet(); set.add("a"); set.add("b"); set.add("c"); Iterator iter = set.iterator(); ① while (iter.hasNext()) { ② String str = (String) iter.next(); ③ System.out.println(str); } } } |
代码解析:
① 经过Set集合的iterator()方法得到该集合的迭代器,迭代器是Iterator类的实例。
② 根据迭代器的hasNext()方法判断集合中是否还有元素,若是有就返回true。
③ 根据迭代器的next()方法得到集合中的元素,并强制类型转换为目标类型。
从上面例子的运行结果,能够看出Set中的元素是无序的。经过Set集合的迭代再来学习Map集合的迭代。
代码演示:Map集合的迭代
import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class MapIter { public static void main(String[] args) { HashMap map = new HashMap(); map.put("001", "小美"); map.put("002", "阿聪"); map.put("003", "小黑"); HashSet keys = (HashSet) map.keySet(); ① Iterator iter = keys.iterator(); while (iter.hasNext()) { String key = (String) iter.next(); ② String value = (String) map.get(key); ③ System.out.println(key + ":" + value); } } } |
代码解析:
① 调用Map集合的keySet方法得到Map集合中的key的集合。
② 得到key。
③ 根据key得到对应的value。
下面经过一个示例演示Set集合中不容许元素重复的特性:
代码演示:向Set集合中添加剧复元素
import java.util.HashSet; import java.util.Iterator; public class Demo2 { public static void main(String[] args) { HashSet set = new HashSet(); set.add("a"); set.add("a"); set.add("c"); System.out.println("集合长度为:" + set.size()); Iterator iter = set.iterator(); while (iter.hasNext()) { String str = (String) iter.next(); System.out.println(str); } } } |
上面的代码输出集合的长度为2,并且集合中的元素只有一个a和c,从结果中能够看出Set集合中的元素是不能重复的。带着这个结论再来看下面的示例:
代码演示:向Set集合中添加剧复元素
import java.util.HashSet; import java.util.Iterator; public class Demo3 { public static void main(String[] args) { Person personA = new Person("001", "Tom"); Person personB = new Person("001", "Tom"); HashSet set = new HashSet(); set.add(personA); set.add(personB); System.out.println("集合中元素个数:" + set.size()); Iterator iter = set.iterator(); while (iter.hasNext()) { Person p = (Person) iter.next(); System.out.println(p.cardId + ":" + p.name); } } } |
程序的运行结果以下:
集合中元素个数:2 001:Tom 001:Tom |
发现程序的运行结果是有“问题”的,由于Set集合中是不容许存放重复的元素的,可是两个Person对象的属性值是彻底相同的,怎么还都能存放进去呢?要找到问题的答案,须要了解下Set集合的存放原理。
图: Set集合存储
从上图中能够看到,Set集合中的元素的无序性,可是Set集合是怎么判断每一个元素的存放的位置呢?在向Set集合中存放元素时,Set集合根据元素的hashCode()方法来获取一个int类型的数据,而后根据这个数据来计算元素在集合中的位置。可是在存储元素时会出现两个元素的hashCode()方法返回值相同的状况,好比上图的对象C和D就出现了这种状况,致使计算出元素在集合中的位置相同,这种状况称之为“冲突”。若是发生了冲突,Set集合会根据发生冲突元素之间调用equals()方法进行比较,若是equals()返回值为true,说明两个元素为相同的元素,这样会致使添加操做无效。若是equals()返回值为false,说明两个元素不相同,这样Set集合会将该元素进行偏移存储。“冲突”发生的频率越高,Set集合的性能就越低,要尽量的避免冲突的发生, 就要在类中重写hashCode()方法,而且要尽量的保证hashCode()方法返回值是惟一的。在重写hashCode()方法时有个技巧,就是让对象中的数值属性和一个素数相乘,并将积相加,对于对象类型,调用其hashCode()方法便可。
对于hashCode()和equals()两个方法有这样的规律,hashCode()方法返回值相同时,equals()方法比较并不必定相等,可是equals()方法比较相等,hashCode()方法返回值是相同的。
代码演示:实现Person对象的equals和hashCode方法
public class Person { String cardId; String name; public Person(String cardId, String name) { this.cardId = cardId; this.name = name; } public int hashCode() { return cardId.hashCode() + name.hashCode(); } public boolean equals(Object obj) { if (obj == null){ return false; } if(obj instanceof Person){ Person other = (Person) obj; if (cardId == null) { if (other.cardId != null){ return false; } } else if (!cardId.equals(other.cardId)){ return false; } if (name == null) { if (other.name != null){ return false; } } else if (!name.equals(other.name)){ return false; } }else{ return false; } return true; } } |
再次运行Demo3的代码,运行结果以下:
集合中元素个数:1 001:Tom |
? Map集合用于保存具备映射关系的数据,即以键值对(key->value)的方式来存储数据。所以在Map集合内部有两个集合,一个集合用于保存Map中的key(键),一个集合用于保存Map中的value(值),其中key和value能够是任意数据类型数据。
? Set集合的特色。
? Set集合的使用及迭代。
? Map集合的迭代。