文章目录
第一章 集合框架
1、前言
在Java语言中,Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类)。所有抽象出来的数据结构和操作(算法)统称为Java集合框架(JavaCollectionFramework)。
Java程序员在具体应用时,不必考虑数据结构和算法实现细节,只需要用这些类创建出来一些对象,然后直接应用就可以了,这样就大大提高了编程效率
2、集合框架包含内容
所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
1、Collection是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。
Collection包含了List和Set两大接口分支。
注意:Map不是Collection的子接口。
(1)List接口
有序的
集合中每一个元素都有它的索引,类似于数组下标,第一个元素的索引值是0。
不唯一
集合中允许有相同的元素。
实现List接口的实现类有LinkedList,ArrayList,Vector和Stack。
添加元素:
add(Object类型值)方法
获取元素
get(下标)方法
获取集合大小(存放元素个数)
size()方法,如果size()返回值为0,代表集合为空
判断集合是否包含某个元素
contains(元素)方法
判断集合是否非空
isEmpty()方法
集合一步转换为数组对象
调用toArray()方法
数组一步转换为集合
使用Arrays.asList(数组);
集合重写了toString方法
ArrayList类
ArrayList实现了可变大小的数组
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加。
底层实现是数组容量自动扩充的,如果是空参构造,长度为0,调用add之后,初始10个长度,后续到达默认容量后,都自动扩容原有长度的一半,如果指定初始容量,不会自动扩容,实际上可变就是把之前数组内容拷贝到新的扩容的数组中
ArrayList没有同步,及不安全。
ArrayList实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问元素的效率比较高.
ArrayList运行存放null值元素
LinkedList类
LinkedList实现了List接口,允许null元素。
LinkedList采用链表存储方式。
由于它的底层是用双向链表实现的,所以没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好
插入、删除元素时效率比较高。
小结:
1)ArrayList底层实现是动态数组,所以遍历和随机访问元素的效率高,末尾插入数据实际跟LinkedList差不多,但如果是中间插入数据,要移动元素,导致数组重新分配,所以效率差,不适合快速插入和删除
2)而对LinkedList而言,插入元素,不管什么位置,这个开销是统一的,分配一个内部Entry对象,但是遍历或者随机访问元素,要移动指针查找,效率低。
(2)Set接口
无序的
不保证维护元素的次序。
唯一
Set中存放的是对象的引用,所以相同的对象是没法添加到集合中,即集合中不允许有相同的元素对象。
注意:虽然Set中元素没有顺序,但是元素在set中的位置是由该元素的HashCode决定的,其具体位置其实是固定的。
简单示例:
public class Test{
public static void main(String[] args) {
Set set = new HashSet();
set.add("Hello");
set.add("world");
set.add("Hello");
System.out.println("集合的尺寸为:"+set.size());
System.out.println("集合中的元素为:"+set.toString());
}
}
运行结果:
集合的尺寸为:2
集合中的元素为:[world, Hello]
分析:由于String类中重写了equals方法,用来比较指向的字符串对象所存储的字符串是否相等。所以这里的第二个Hello是加不进去的。
HashSet :
它不允许出现重复元素;不保证集合中元素的顺序
允许包含值为null的元素,但最多只能有一个null元素。
HashSet按Hash算法来存储集合的元素,因此具有很好的存取和查找性能。
HashSet使用和理解中容易出现的误区:
a.HashSet中存放null值
HashSet中是允许存入null值的,但是在HashSet中仅仅能够存入一个null值。
b.HashSet中存储元素的位置是固定的
HashSet中存储的元素的是无序的,这个没什么好说的,但是由于HashSet底层是基于Hash算法实现的,使用了hashcode,所以HashSet中相应的元素的位置是固定的。
TreeSet :
可以实现排序等功能的集合,它在讲对象元素添加到集合中时会自动按照某种比较规则将其插入到有序的对象序列中,并保证该集合元素组成按照“升序”排列。
TreeSet是一个有序集合,其底层是基于TreeMap实现的,非线程安全。TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。当我们构造TreeSet时,若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
注意:TreeSet集合不是通过hashcode和equals函数来比较元素的.
小结:
a) 在对大量信息进行检索的时候,TreeSet比ArrayList更有效率。
b) TreeSet是用树形结构来存储信息的,每个节点都会保存一下指针对象,分别指向父节点,左分支,右分支,相比较而言,ArrayList就是一个含有元素的简单数组了,正因为如此,它占的内存也要比ArrayList多一些。
c) 想TreeSet插入元素也比ArrayList要快一些,因为当元素插入到ArrayList的任意位置时,平均每次要移动一半的列表(普遍的都是,set查询慢,插入快,list查询快,插入慢)。
2、Map接口
存放的内容为:key-value键值对。
Map中的每一个元素包含“一个key”和“key对应的value”。
Map主要用于存储健值对,根据键得到值,因此不允许键重复,但允许值重复。
HashMap 是一个 最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。
HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;
HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。
常用方法:
添加键值对的方法
使用put(key, value)方法
获取键值对
使用get(key)方法
获取结合键值对的个数
使用size()方法
判断map集合中是否包含某个键值对
使用containsKey(key)方法
判断map集合中是否包含某个值
使用containsValue(value)方法
删除键值对
使用remove(key)方法
获取集合中所有的键
使用keySet()方法
获取集合中所有的值
使用values()方法
Map中可以存放null值作为key,但只能有一个key为null
Map中可以存放多个null值作为value,没有限制
map中存放键值对,如果发现key已经存在,会覆盖对应原有的值
map集合也重写了toString方法
3、小结
List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。
Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)
Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value,key唯一,值不唯一。
4、迭代器Iterator。
它是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动:
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。
(2) 使用next()获得序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素
(4) 使用remove()将迭代器新返回的元素删除。
5、遍历集合的三种方式:
1、迭代器
Set keys = carMap.keySet(); //取出所有key的集合
Iterator it = keys.iterator(); //获取Iterator对象
while(it.hasNext()){
//取出key
String key=(String)it.next(); //根据key取出对应的值
String val=(String)carMap.get(key);
System.out.println(key+"\t"+val);
}
2、增强for
for(元素类型t 元素变量x : 数组或集合对象){
引用了x的java语句
}
示例:用list或者map为例
for(Object obj : list){
System.out.println(obj);
}
或者
for (Object key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
3、键值对
for (Object object : tempMap.entrySet()) {
Map.Entry entry = (Map.Entry)object;
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
6、 泛型集合
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
特点:
1)适用于多种数据类型执行相同的代码
2)泛型中的类型在使用时指定
3)泛型归根到底就是“模版”
优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。
示例:list集合:
定义Car这类,属性是brand,color,price;
然后使用泛型集合
//创建ArrayList集合对象并存储狗狗
List<Car> cars = new ArrayList<Car>();
Car car1 = new Car("Bmw", "黑色", 100);
Car car2 = new Car("Benz", "白色", 200);
cars.add(car1);
cars.add(car2);
// cars.add("hello"); -- 会报错
// 获取的元素直接为泛型对象,不需要强转
Car car = cars.get(2);
//使用foreach遍历cars对象
for(Car car : cars){
System.out.println("--" + car.getBrand() + "--" + car.getColor());
}
map和set集合也是类似,详见当日代码
自定义泛型扩展:
/**
* 自定义泛型类
*
* @author Administrator
* @param <T>
*/
public class Student<T> {
private T javase;
//private static T javaee; // 泛型不能使用在静态属性上
public Student() {
}
public Student(T javase) {
this();
this.javase = javase;
}
public T getJavase() {
return javase;
}
public void setJavase(T javase) {
this.javase = javase;
}
}
/**
* 自定义泛型的使用
* 在声明时指定具体的类型
* 不能为基本类型
* @author Administrator
*
*/
public class Test {
public static void main(String[] args) {
// 不能为基本类型,编译时异常,如:Student<int> Student = new Student<int>();
// 要用int的封装类型
Student<Integer> student = new Student<Integer>();
student.setJavase(85);
System.out.println(student.getJavase());
}
}
扩展: 遍历map集合的三种方式:
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
//第一种:普遍使用,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
//第二种
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第三种:推荐,尤其是容量大时
System.out.println("通过Map.entrySet遍历key和value");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
}
7、Collections
和Collection不同,前者是集合的操作类,是一个提供对结合之间操作的工具类,后者是集合接口。
Collection方法,需要T类的对象实现Comparable接口
sort()方法:给集合排序
reverse()方法:反转集合中元素的顺序
min(Collection)方法:使用自然排序法,获取集合最小元素
binarySearch(Collection,Object)方法:查找指定集合中的元素,返回所查找元素的索引
8、Comparable接口实现对象集合排序:
示例:
public class Car implements Comparable<Object> {
private String brand;
private String color;
private double price;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public int compareTo(Object obj) {
// 通过价格给汽车排序
if(null == obj){
return -1;
}
Car car = (Car)obj;
if(this.price == car.getPrice()){
return 0;
} else if (this.price > car.getPrice()) {
return 1;
} else {
return -1;
}
}
public static void main(String[] args) {
Car car1 = new Car();
car1.setPrice(5);
Car car2 = new Car();
car2.setPrice(2);
Car car3 = new Car();
car3.setPrice(2);
Car car4 = new Car();
car4.setPrice(3);
Car car5 = new Car();
car5.setPrice(1);
List<Car> cars = new ArrayList<Car>();
cars.add(car1);
cars.add(car2);
cars.add(car3);
cars.add(car4);
cars.add(car5);
System.out.println("按照价格排序前:");
for (Car car : cars) {
System.out.println(car.getPrice());
}
// 给汽车排序
Collections.sort(cars);
System.out.println("按照价格排序后:");
for (Car car : cars) {
System.out.println(car.getPrice());
}
}
}
9、补充内容
Vector、ArrayList、LinkedList:
1、Vector:
Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
2、ArrayList:
a. 当操作是在一列数据的后面添加数据而不是在前面或者中间,并需要随机地访问其中的元素时,使用ArrayList性能比较好。
b. ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
3、LinkedList:
a. 当对一列数据的前面或者中间执行添加或者删除操作时,并且按照顺序访问其中的元素时,要使用LinkedList。
b. LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
Vector和ArrayList在使用上非常相似,都可以用来表示一组数量可变的对象应用的集合,并且可以随机的访问其中的元素。
4、Vector与ArrayList比较
1). 性能上
ArrayList底层数据结构是数组,适合随机查找和遍历,不适合插入和删除,线程不安全,效率高。
Vector是线程安全的集合类,Vector类对集合的元素操作时都加了synchronized,保证线程安全。
2). 同步性
Vectors是可同步的,是线程安全的。ArrayList是不可同步的,不是线程安全的。所以,一般单线程推荐用ArrayList,多线程中则用Vector
3). 数据增长
往一个ArrayList或者Vector里插入一个元素时,如果内部数组空间不够,ArrayList或Vector会扩展它的大小。Vector在默认情况下增长一倍的大小,而ArrayList增加50%的大小。
HashTable、HashMap、HashSet:
HashTable和HashMap采用的存储机制是一样的,不同的是:
1、HashMap:
a. 采用数组方式存储key-value构成的Entry对象,无容量限制;
b. 基于key hash查找Entry对象存放到数组的位置,对于hash冲突采用链表的方式去解决;
c. 在插入元素时,可能会扩大数组的容量,在扩大容量时须要重新计算hash,并复制对象到新的数组中;
d. 是非线程安全的;
e. 遍历使用的是Iterator迭代器;
2、HashTable:
a. 是线程安全的;
b. 无论是key还是value都不允许有null值的存在;在HashTable中调用Put方法时,如果key为null,直接抛出NullPointerException异常;
c. 遍历使用的是Enumeration列举;
3、HashSet:
a. 基于HashMap实现,无容量限制;
b. 是非线程安全的;
c. 不保证数据的有序;
10.集合框架常用方法示例
//复习集合框架常用方法
public class Test {
/**
* ArraysList集合常用方法
* @param args
*/
public static void testArrayList(){
List<String> list = new ArrayList<String>();
//添加元素
list.add("H");
list.add("E");
list.add("L");
list.add("L");
list.add("O");
for(int i =0;i<list.size();i++){
//靠下标取值
System.out.print(list.get(i));
}
System.out.println();
System.out.println(list.toString());
System.out.println(list.contains("O"));
System.out.println(list.isEmpty());
list.add(null);
list.add(null);
System.out.println(list);
Object[] arr = list.toArray();//可以转换为Object类型的数组
List<Object> list1 =Arrays.asList(arr);//可以转换为Object类型的集合
System.out.println(list1);
}
/**
* LinkedList集合常用方法
* @param args
*/
public static void testListedList(){
LinkedList<String> list = new LinkedList<String>();
// 添加元素
list.add("H");
list.add("E");
list.add("L");
list.add("L");
list.add("O");
for (int i = 0; i < list.size(); i++) {
// 靠下标取值
System.out.print(list.get(i));
}
System.out.println();
list.addFirst("F");
list.addLast("L");
System.out.println(list.toString());
System.out.println(list.getFirst());
System.out.println(list.getLast());
list.removeFirst();
list.removeLast();
System.out.println(list);
list.remove();//移去第一个
System.out.println(list);
System.out.println(list.isEmpty());
System.out.println(list.contains("L"));
Object[] obj = list.toArray();//集合转化为数组
List<Object> list1 = Arrays.asList(obj);//数组转换为集合
System.out.println(list1);
}
/**
* Set集合常用方法
* @param args
*/
public static void testSet(){
Set<String> set = new HashSet<String>();
set.add("H");
set.add("E");
set.add("L");
set.add("L");//重复添加不会报编译错误,但是add内部有equals方法,会判断值是否相同,相同则不放进去
set.add("O");
System.out.println(set.size());
System.out.println(set.toString());
System.out.println(set);
System.out.println(set.isEmpty());
System.out.println(set.contains("L"));
//加了三个null,但是只有一个
set.add(null);
set.add(null);
set.add(null);
System.out.println(set.size());
System.out.println(set.toString());
Object[] obj = set.toArray();//可以将set转换为数组
//
List<Object>list =Arrays.asList(obj);//Arrays工具类可以将数组转换位List,不能转为Set
System.out.println(list);
//遍历方式1
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.print(s);
}
System.out.println();
//遍历方式2
for(String str : set){
System.out.print(str);
}
}
/**
* Map键值对集合常用方法
* @param args
*/
public static void testMap(){
Map<String,String> map = new HashMap<String,String>();
map.put("1", "埃弗拉");
map.put("2", "撒地方");
map.put("3", "士大夫");
map.put("4", "爱的");
map.put("5", null);
map.put("6", null);
map.put("1", "拉");
System.out.println(map.size());
System.out.println(map.toString());
System.out.println(map);
System.out.println(map.containsKey("1"));
System.out.println(map.containsValue("士大夫"));
System.out.println(map.keySet());
System.out.println(map.values());
map.remove("1");
System.out.println(map);
map.put(null, null);
map.put(null, "1");
map.put(null, "2");
System.out.println(map);
//循环遍历map集合
//方式1
Iterator<String> it = map.keySet().iterator();
while(it.hasNext()){
String key = it.next();
System.out.println("键为:"+key+",值为:"+map.get(key));
}
System.out.println("---------------------------------------");
//方式2
for(String key :map.keySet()){
System.out.println("键为:"+key+",值为:"+map.get(key));
}
System.out.println("---------------------------------------");
//方式3
for(Map.Entry<String, String> entry:map.entrySet()){
System.out.println("键为:"+entry.getKey()+",值为:"+entry.getValue());
}
}
/**
* Collections工具类常用方法:Collections工具类只能用于List集合
* @param args
*/
public static void testCollections(){
LinkedList<String> list = new LinkedList<String>();
// 添加元素
list.add("H");
list.add("E");
list.add("L");
list.add("L");
list.add("O");
Collections.sort(list);
System.out.println(list);
System.out.println(Collections.binarySearch(list, "O"));
Collections.reverse(list);
System.out.println(list);
System.out.println(Collections.max(list));
List<String> list1 = new ArrayList<String>();
//添加元素
list1.add("H");
list1.add("E");
list1.add("L");
list1.add("L");
list1.add("O");
Collections.sort(list1);
System.out.println(list1);
System.out.println(Collections.binarySearch(list1, "O"));
Collections.reverse(list1);
System.out.println(list1);
}
public static void main(String[] args) {
testArrayList();
System.out.println("------------------------------------");
testListedList();
System.out.println("------------------------------------");
testSet();
System.out.println("------------------------------------");
testMap();
System.out.println("------------------------------------");
testTT();
System.out.println("------------------------------------");
testCollections();
System.out.println("------------------------------------");
testSortObject();
}
}