Java学习记录(中级)——【3】、集合框架

先上一张简化的集合框架关系图:↓ ↓ ↓ 

(1)、Collection 接口

Collection是 List Set Queue(先进先出队列)Deque(双向链表) 等接口的父接口。

【注:Collection 和 Map 之间没有关系,Collection是放一个一个对象的,Map 是放键值对的】
【注:Deque 继承 Queue,间接得继承了 Collection】

(2)、Collections 类

Collections是一个类,容器的工具类,就如同 Arrays 是数组的工具类一样,它也可以对容器进行一些操作。

方法名关键字 功能
reverse 反转
shuffle 混淆(打乱顺序)
sort 排序
swap 交换指定下标位置的数据
rotate 把集合中的数据向右滚动指定单位的长度
synchronizedList 非线程安全的集合转换为线程安全的集合
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsTest {

	public static void main(String[] args) {
		// 初始化List
		List<Integer> list = new ArrayList<>();
		for (int i = 0; i < 10; i++) {
			list.add(i+1);
		}
		// 原List
		System.out.println("原list:\t\t\t" + list);
		
		
		
		System.out.println("******************************");
		
		
		
		// 反转
		Collections.reverse(list);
		System.out.println("反转后的list:\t\t" + list);
		
		
		
		System.out.println("******************************");
		
		
		
		// 混淆
		Collections.shuffle(list);
		System.out.println("混淆后的list:\t\t" + list);
		
		
		
		System.out.println("******************************");
		
		
		
		// 排序
		Collections.sort(list);
		System.out.println("排序后的list:\t\t" + list);
		
		
		
		System.out.println("******************************");
		
		
		
		// 交换两个位置的数据
		Collections.swap(list, 0, 1);
		System.out.println("交换0和1下标的数据后的list:\t" + list);
		
		
		
		System.out.println("******************************");
		
		
		
		// 先恢复原list
		Collections.swap(list, 0, 1);
		System.out.println("恢复后的list:\t\t" + list);
		// 滚动
		Collections.rotate(list, 3);
		System.out.println("向右滚动3个单位后的list:\t" + list);
		
		
		
		System.out.println("******************************");
		
		
		
		// 线程安全化
		List<Integer> newList = Collections.synchronizedList(list);
		// 把原来不是线程安全的list转换为了现在线程安全的newList
		
	}
	
}

输出结果:

(3)、List 接口与 ArrayList 类

List 集合中的对象按照一定的顺序排放,里面的内容可以重复;

扫描二维码关注公众号,回复: 12045438 查看本文章

List 接口实现的类有:ArrayList(实现动态数组)Vector(实现动态数组)LinkedList(实现链表)Stack(实现堆栈)

[1]、使用数组的局限性

如果要存放多个对象,可以使用数组,但是数组有局限性!!!

比如 :声明长度是10的数组,不用的数组就浪费了,超过10的个数,又放不下!!!

[2]、ArrayList

为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是 : ArrayList 
容器的容量"capacity"会随着对象的增加,自动增长。

[3]、ArrayList 的常用方法

方法名关键字 功能
contains 判断是否存在
add 添加对象
addAll 把另一个容器所有对象都加进来
remove 删除对象
clear 清空
set 替换某位置的对象(重赋值)
get 获取指定位置的对象
indexOf 获取对象所处的位置
toArray 转换为数组
size 获取大小

[4]、迭代器遍历

使用迭代器Iterator遍历集合中的元素,原理如下:↓ ↓ ↓ 

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {

	public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        
        for (int i = 0; i < 5; i++) {
			list.add("String " + i);
		}
        System.out.println("最初的list为:" + list);
        
        // 获取List的迭代器
        Iterator<String> iterator = list.iterator();
        
        //从最开始的位置判断"下一个"位置是否有数据
        //如果有就通过next取出来,并且把指针向下移动
        //直到"下一个"位置没有数据
        
        System.out.println("--------使用while的iterator-------");
        // 利用while循环遍历
        while(iterator.hasNext()) {
        	String str = iterator.next();
        	System.out.println(str);
        }
        
        System.out.println("--------使用for的iterator-------");
        // 利用for循环遍历
        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
			String str = it.next();
			if(str.equals("String 2")) {
				it.remove();
				System.out.println(str + "(当字符串为“String 2”时从迭代器中删除它)");
			}else {
				System.out.println(str);
			}
		}
        System.out.println("删除“String 2”后的list为:" + list);
        
         
    }

}

输出结果:

可以利用迭代器删除list中的元素。

(4)、LinkedList

序列分为: 先进先出FIFO先进后出FILO 
FIFO在Java中又叫Queue 队列 
FILO在Java中又叫Stack 栈

[1]、与ArrayList一样,LinkedList也实现了List接口;

[2]、但同时,LinkedList 除了实现了 List 接口外,LinkedList 还实现了双向链表结构 Deque,可以很方便的在头尾插入删除数据;

import java.util.LinkedList;
import java.util.List;

public class CollectionsTest {
	
	public static void main(String[] args) {
        
        //LinkedList是一个双向链表结构的list
        LinkedList<String> list =new LinkedList<>();
         
        //所以可以很方便的在头部和尾部插入数据
        //在最后插入新的字符串
        list.addLast("String 1");
        list.addLast("String 2");
        list.addLast("String 3");
        System.out.println("加入数据后的list:\t\t" + list);
         
        //在最前面插入新的字符串
        list.addFirst("String X");
        System.out.println("最前面插入新的字符串后的list:\t" + list);
         
        //查看最前面的字符串
        System.out.println("最前面的字符串:\t\t " + list.getFirst());
        //查看最后面的字符串
        System.out.println("最后面的字符串:\t\t " + list.getLast());
         
        //查看不会导致元素被删除
        System.out.println("再次查看list:\t\t" + list);
        //取出最前面的字符串
        System.out.println("取出来的最前面的字符串:\t " + list.removeFirst());
         
        //取出最后面的字符串
        System.out.println("取出来的最后面的字符串:\t " + list.removeLast());
         
        //取出会导致元素被删除
        System.out.println("再次查看list:\t\t" + list);
         
    }
	
}

输出结果:

[3]、LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)

Queue是先进先出队列 FIFO,常用方法:

offer        在最后添加元素
poll         取出第一个元素
peek         查看第一个元素
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class CollectionsTest {

	public static void main(String[] args) {

		// 和ArrayList一样,LinkedList也实现了List接口
		List<String> list = new LinkedList<>();

		// 同时,LinkedList还实现了Deque接口
		// Deque代表双向链表
		Deque<String> deque = new LinkedList<>();

		// 同时,LinkedList也实现了Queue这个接口
		// Queue代表FIFO 先进先出的队列
		Queue<String> queue = new LinkedList<>();

		for (int i = 0; i < 2; i++) {
			queue.offer("String " + i); // 在最后添加元素
		}
		for (int i = 0; i < 3; i++) {
			queue.add("String " + (i + 2)); // 与offer()同样的效果,添加元素
		}    //
		System.out.println("利用offer()方法或add()方法添加元素。");
		System.out.println("添加元素后的queue为:\t" + queue);
		System.out.println();

		String first = queue.peek();    //
		System.out.println("利用peek()方法查看第一个元素。");
		System.out.println("查看第一个元素:\t\t " + first);
		System.out.println();

		queue.poll();    //
		System.out.println("利用poll()方法取出第一个元素。");
		System.out.println("取出第一个元素......");
		System.out.println("第一个元素取出后的queue为:\t" + queue);

	}

}

输出结果:

(5)、二叉树

二叉树由各种节点组成
二叉树特点:
       每个节点都可以有左子节点、右子节点、值。

public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;
    // 值
    public Object value;
}

[1]、二叉树-插入数据-原理:

假设通过二叉树对如下10个随机数进行排序
67,7,30,73,10,0,78,81,10,74
排序的第一个步骤是把数据插入到该二叉树中
插入基本逻辑是,小、相同的放左边,大的放右边
1. 67 放在根节点
2. 7 比 67小,放在67的左节点
3. 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点
4. 73 比67大, 放在67得右节点
5. 10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。
...
...
9. 10比67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,找到30的左节点10,10和10一样大,放在左边

public class Node {

	/**
	 * 左子节点
	 */
	public Node leftNode;
	/**
	 * 右子节点
	 */
	public Node rightNode;
	/**
	 * 值
	 */
	public Object value;

	/**
	 * 插入数据
	 * @param v
	 */
	public void add(Object v) {
		// 如果当前节点没有值,就把数据放在当前节点上
		if (value == null) {
			value = v;
		} else { // 如果当前节点有值,就进行判断,新增的值与当前的值的大小关系
			// 新增的值,比当前值小或者相同
			if ((Integer) value - (Integer) v >= 0) {
				if (leftNode == null) {
					leftNode = new Node();
				}
				leftNode.add(v);
			}
			// 新增的值,比当前值大
			else {
				if (rightNode == null) {
					rightNode = new Node();
				}
				rightNode.add(v);
			}
		}
	}

	public static void main(String[] args) {

		int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
		Node roots = new Node();
		for (int number : randoms) {
			roots.add(number);
		}

	}

}

[2]、二叉树-遍历:

通过上一个步骤的插入行为,实际上,数据就已经排好序了。 接下来要做的是看,把这些已经排好序的数据,遍历成我们常用的List或者数组的形式

二叉树的遍历分先序,中序,后序
【先序】即: 中间的数 遍历后放在 左边
【中序】即: 中间的数 遍历后放在 中间
【后序】即: 中间的数 遍历后放在 右边

import java.util.ArrayList;
import java.util.List;

public class Node {

	/**
	 * 左子节点
	 */
	public Node leftNode;
	/**
	 * 右子节点
	 */
	public Node rightNode;
	/**
	 * 值
	 */
	public Object value;

	/**
	 * 插入数据
	 * @param v
	 */
	public void add(Object v) {
		// 如果当前节点没有值,就把数据放在当前节点上
		if (value == null) {
			value = v;
		} else { // 如果当前节点有值,就进行判断,新增的值与当前的值的大小关系
			// 新增的值,比当前值小或者相同
			if ((Integer) value - (Integer) v >= 0) {
				if (leftNode == null) {
					leftNode = new Node();
				}
				leftNode.add(v);
			}
			// 新增的值,比当前值大
			else {
				if (rightNode == null) {
					rightNode = new Node();
				}
				rightNode.add(v);
			}
		}
	}

	// 先序遍历所有的节点
	public List<Object> valuesOfLeft() {
		
		List<Object> values = new ArrayList<>();
		
		// 当前节点
		values.add(value);
		
		// 左节点的遍历结果
		if (leftNode != null) {
			values.addAll(leftNode.valuesOfLeft());
		}
		
		// 右节点的遍历结果
		if (rightNode != null) {
			values.addAll(rightNode.valuesOfLeft());
		}
		
		return values;
		
	}

	// 中序遍历所有的节点
	public List<Object> valuesOfCenter() {
		
		List<Object> values = new ArrayList<>();
		
		// 左节点的遍历结果
		if (leftNode != null) {
			values.addAll(leftNode.valuesOfCenter());
		}
		
		// 当前节点
		values.add(value);
		
		// 右节点的遍历结果
		if (rightNode != null) {
			values.addAll(rightNode.valuesOfCenter());
		}
		
		return values;
		
	}

	// 后序遍历所有的节点
	public List<Object> valuesOfRight() {
		
		List<Object> values = new ArrayList<>();
		
		// 左节点的遍历结果
		if (leftNode != null) {
			values.addAll(leftNode.valuesOfRight());
		}
		
		// 右节点的遍历结果
		if (rightNode != null) {
			values.addAll(rightNode.valuesOfRight());
		}
		
		// 当前节点
		values.add(value);
		
		return values;
		
	}

	public static void main(String[] args) {

		int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
		Node roots = new Node();
		for (int number : randoms) {
			roots.add(number);
		}

		System.out.println("先序遍历的二叉树:\t" + roots.valuesOfLeft());
		System.out.println("中序遍历的二叉树:\t" + roots.valuesOfCenter());
		System.out.println("后序遍历的二叉树:\t" + roots.valuesOfRight());

	}

}

输出结果:

(6)、HashMap

HashMap储存数据的方式是—— 键值对

对于HashMap而言,key是唯一的,不可以重复的。 
所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。 
不过,同一个对象可以作为值插入到map中,只要对应的key不一样

(7)、HashSet

Set中的元素,不能重复

[1]、没有顺序

Set中的元素,没有顺序。 
严格的说,是没有按照元素的插入顺序排列

HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。

换句话说,同样是插入0-9到HashSet中, 在JVM的不同版本中,看到的顺序都是不一样的。

import java.util.HashSet;

public class HashSetTest {

	public static void main(String[] args) {

		HashSet<String> set = new HashSet<>();
		
		set.add("hello");
		set.add("world");
		set.add("!!!");
		
		System.out.println(set);
		
	}

}

运行结果:

[2]、遍历

Set不提供get()来获取指定位置的元素 
所以遍历需要用到迭代器,或者增强型for循环

[3]、HashSet和HashMap的关系

通过观察HashSet的源代码,
可以发现HashSet自身并没有独立的实现,而是在里面封装了一个Map.
HashSet是作为Map的key而存在的
而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。

private static final Object PRESENT = new Object();
package collection;

import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
	// HashSet里封装了一个HashMap
	private HashMap<E, Object> map;

	private static final Object PRESENT = new Object();

	// HashSet的构造方法初始化这个HashMap
	public HashSet() {
		map = new HashMap<E, Object>();
	}

	// 向HashSet中增加元素,其实就是把该元素作为key,增加到Map中
	// value是PRESENT,静态,final的对象,所有的HashSet都使用这么同一个对象
	public boolean add(E e) {
		return map.put(e, PRESENT) == null;
	}

	// HashSet的size就是map的size
	public int size() {
		return map.size();
	}

	// 清空Set就是清空Map
	public void clear() {
		map.clear();
	}

	// 迭代Set,就是把Map的键拿出来迭代
	public Iterator<E> iterator() {
		return map.keySet().iterator();
	}

}

(8)、Set

HashSet                 无序
LinkedHashSet     按照插入顺序
TreeSet                  从小到大排序

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;

public class HashSetTest {
	
	public static void main(String[] args) {

        HashSet<String> numberSet1 =new HashSet<>();
        //HashSet中的数据不是按照插入顺序存放
        numberSet1.add("bbbbb");
        numberSet1.add("aaaaa");
        numberSet1.add("ccccc");
        System.out.println(numberSet1);
          
        LinkedHashSet<String> numberSet2 =new LinkedHashSet<>();
        //LinkedHashSet中的数据是按照插入顺序存放
        numberSet2.add("bbbbb");
        numberSet2.add("aaaaa");
        numberSet2.add("ccccc");
        System.out.println(numberSet2);

        TreeSet<String> numberSet3 =new TreeSet<>();
        //TreeSet 中的数据是进行了排序的
        numberSet3.add("bbbbb");
        numberSet3.add("aaaaa");
        numberSet3.add("ccccc");
        System.out.println(numberSet3);
          
    }

}

运行结果:

(9)、ArrayList和LinkedList的区别

ArrayList 是【顺序表】结构,【查找数据快】,【插入、删除数据慢】;
LinkedList 是【链表】结构,【查找数据慢】,【插入、删除数据快】。

(10)、ArrayList和HashSet的区别

ArrayList  有顺序,数据可以重复;
HashSet  无顺序,数据不能够重复。

HashSet 重复判断标准是:
首先看hashcode是否相同
如果hashcode不同,则认为是不同数据
如果hashcode相同,再比较equals,如果equals相同,则是相同数据,否则是不同数据

(11)、HashMap 和HashTable 的区别

HashMap 和 Hashtable 都实现了Map接口,都是键值对保存数据的方式
区别1: 
HashMap 可以存放 null 值
Hashtable 不能存放null 值
区别2:
HashMap不是线程安全的类
Hashtable是线程安全的类

import java.util.HashMap;
import java.util.Hashtable;

public class MapTableTest {

	public static void main(String[] args) {

		// HashMap 可以存放 null 值
		HashMap<String, Integer> map = new HashMap<>();
		map.put("first", null);
		System.out.println(map);

		// Hashtable 不能存放null 值
		Hashtable<String, Integer> table = new Hashtable<>();
		table.put("first", null);
		System.out.println(table);

	}

}

运行结果:

(12)、HashCode原理

[1]、效率比较

HashCode带来的差别如下:

[2]、HashCode 原理

比如要在一本英汉字典中找一个单词对应的中文意思,假设单词是Lengendary,首先在目录找到Lengendary在第 555页。 

然后,翻到第555页,这页不只一个单词,但是量已经很少了,逐一比较,很快就定位目标单词Lengendary。

555相当于就是Lengendary对应的hashcode

[3]、分析HashMap性能卓越的原因

-----hashcode概念-----
所有的对象,都有一个对应的hashcode(散列值)
比如字符串“gareen”对应的是1001 (实际上不是,这里是方便理解,假设的值)
比如字符串“temoo”对应的是1004
比如字符串“db”对应的是1008
比如字符串“annie”对应的也是1008

-----保存数据-----
准备一个数组,其长度是2000,并且设定特殊的hashcode算法,使得所有字符串对应的hashcode,都会落在0-1999之间
要存放名字是"gareen"的英雄,就把该英雄和名称组成一个键值对,存放在数组的1001这个位置上
要存放名字是"temoo"的英雄,就把该英雄存放在数组的1004这个位置上
要存放名字是"db"的英雄,就把该英雄存放在数组的1008这个位置上
要存放名字是"annie"的英雄,然而 "annie"的hashcode 1008对应的位置已经有db英雄了,那么就在这里创建一个链表,接在db英雄后面存放annie

-----查找数据-----
比如要查找gareen,首先计算"gareen"的hashcode是1001,根据1001这个下标,到数组中进行定位,(根据数组下标进行定位,是非常快速的) 发现1001这个位置就只有一个英雄,那么该英雄就是gareen.
比如要查找annie,首先计算"annie"的hashcode是1008,根据1008这个下标,到数组中进行定位,发现1008这个位置有两个英雄,那么就对两个英雄的名字进行逐一比较(equals),因为此时需要比较的量就已经少很多了,很快也就可以找出目标英雄
这就是使用hashmap进行查询,非常快原理。


这是一种用空间换时间的思维方式

åæHashMapæ§è½åè¶çåå 

[4]、HashSet判断是否重复

HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢?
根据HashSet和HashMap的关系,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,所以本质上就是判断HashMap的key是否重复。

再通过上一步的学习,key是否重复,是由两个步骤判断的:
hashcode是否一样
        如果hashcode不一样,就是在不同的坑里,一定是不重复的
        如果hashcode一样,就是在同一个坑里,还需要进行equals比较
                如果equals一样,则是重复数据
                如果equals不一样,则是不同数据。

(13)、比较器

Collections.sort()方法可以实现对集合内元素的排序

但是,假如,有一个集合,里面的元素全是Person类型的对象,其属性有name,age,address等,到底按那个属性进行排序?

这就要用到比较器!!!

[1]、方法一

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

public class ComparatorTest {

	public static void main(String[] args) {

		Random r = new Random();
		List<Person> persons = new ArrayList<>();

		for (int i = 0; i < 10; i++) {
			persons.add(new Person("张" + i, r.nextInt(20) + 20, "西安"));
		}
		System.out.println("初始化后的集合:");
		System.out.println(persons);

		// 直接调用sort会出现编译错误,因为Person有各种属性
		// 到底按照哪种属性进行比较,Collections也不知道,不确定,所以没法排
		// Collections.sort(persons);

		// 引入Comparator,指定比较的算法
		Comparator<Person> c = new Comparator<Person>() {
			@Override
			public int compare(Person p1, Person p2) {
				// 按照hage进行排序
				if (p1.age >= p2.age)
					return 1; // 正数表示p1比hp2要大
				else
					return -1;
			}
		};
		Collections.sort(persons, c);
		System.out.println("按照年龄排序后的集合:");
		System.out.println(persons);

	}

}

class Person {
	public String name;
	public int age;
	public String address;

	public Person(String name, int age, String address) {
		this.name = name;
		this.age = age;
		this.address = address;
	}

	@Override
	public String toString() {
		return "\n" + "Name : " + name + "\tAge : " + age + "\tAddress : " + address;
	}

}

执行结果:

[2]、方法二

使Person类实现Comparable接口
在类里面提供比较算法
Collections.sort就有足够的信息进行排序了,也无需额外提供比较器Comparator

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

public class ComparatorTest {

	public static void main(String[] args) {

		Random r = new Random();
		List<Person> persons = new ArrayList<>();

		for (int i = 0; i < 10; i++) {
			persons.add(new Person("张" + i, r.nextInt(20) + 20, "西安"));
		}
		System.out.println("初始化后的集合:");
		System.out.println(persons);

		// 直接调用sort会出现编译错误,因为Person有各种属性
		// 到底按照哪种属性进行比较,Collections也不知道,不确定,所以没法排
		// Collections.sort(persons);

		// 引入Comparator,指定比较的算法
		Comparator<Person> c = new Comparator<Person>() {
			@Override
			public int compare(Person p1, Person p2) {
				// 按照hage进行排序
				if (p1.age >= p2.age)
					return 1; // 正数表示p1比hp2要大
				else
					return -1;
			}
		};
		
		//Person类实现了接口Comparable,即自带比较信息。
        //Collections直接进行排序,无需额外的Comparator
		Collections.sort(persons);
		System.out.println("按照年龄排序后的集合:");
		System.out.println(persons);

	}

}

class Person implements Comparable<Person> {
	public String name;
	public int age;
	public String address;

	public Person(String name, int age, String address) {
		this.name = name;
		this.age = age;
		this.address = address;
	}

	@Override
	public String toString() {
		return "\n" + "Name : " + name + "\tAge : " + age + "\tAddress : " + address;
	}

	@Override
	public int compareTo(Person o) {
		if (age > o.age) {
			return 1;
		}
		return -1;
	}

}

运行结果:

猜你喜欢

转载自blog.csdn.net/qq_37164975/article/details/82688248