1、集合的理解与优点
1.1 集合
可以把集合理解为多种数据放在一起的数据类型或者叫数据结构
1.2 集合和数组的区别
数组:只能指定长度,保存同一类型数据在这里插入图片描述
集合:长度可变,可以保存不同类型的数据。
public class Arr {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//数组增加数据
Person[] people = new Person[1];
people[0] = new Person();
Person[] people1 = new Person[people.length+1];
for (int i = 0; i < people1.length-1; i++) {
people1[i] = people[i] ;
}
people1[people1.length-1] = new Person();
//集合增加数据
Collection collection = new ArrayList();
collection.add(new Person("jack",20));
collection.add(new Person("tom",21));
System.out.println(collection);
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1.3 集合的优点
1)可以动态保存任意多个对象,使用比较方便。
2)提供了一系列方便的操作对象的方法:add、remove、set、get等。
3)使用集合添加,删除新元素的代码简洁。
2、 集合的框架体系
集合主要分两组,单列(在集合里面放的单个对象)和双列(对象在集合里面以键值对形式存在)
2.1 单列集合
//单列
ArrayList arrayList = new ArrayList();
arrayList.add("Jack");
arrayList.add("Tom");
2.2 双列集合
//双列集合 hashmap等
HashMap hashMap = new HashMap();
hashMap.put("name","tom");
3、connection
3.1 connection的方法
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//以ArrayList集合类来演示
//由于 ArrayList 实现了 Collection 接口,所以可以将 ArrayList类型的对象赋值给 Collection 类型的引用变量。这体 //现了 Java 的多态性
Collection collection = new ArrayList();
//add 添加单个元素
collection.add("Tom");
collection.add("Jery");
collection.add(10);
collection.add(true);
System.out.println("collection=" + collection);
//remove 删除单个元素
collection.remove("Tom");
System.out.println("collection=" + collection);
//contains 查找元素是否存在
System.out.println(collection.contains("Tom"));//false
//isEmpty 判断集合是否为空
System.out.println(collection.isEmpty());//false
//size 获取元素个数
System.out.println(collection.size());//3
//clear 清空
collection.clear();
System.out.println("collection=" + collection);//[]
//addAll 添加多个元素
Collection collection1 = new ArrayList();
collection1.add("面包");
collection1.add("薯条");
collection1.add("汉堡");
collection.addAll(collection1);
System.out.println("collection=" + collection);
//containsAll 检查多个元素是否存在
System.out.println(collection.containsAll(collection1));//true
//removeAll 删除多个元素
collection.add("火锅");
System.out.println("collection=" + collection);
collection.removeAll(collection1);
System.out.println("collection=" + collection);//火锅
}
}
3.2 collection的遍历
3.2.1 迭代器遍历
1)lterator对象称为迭代器,主要用于遍历 Collection 集合中的元素。
2)所有实现了Collection接口的集合类都有一个iterator()方法,用以返回
一个实现了lterator接口的对象,即可以返回一个迭代器。
3)迭代器的执行原理
Iterator iterator = collection.iterator();//得到一个集合的迭代器
//hasNext() 判断是否还有下一个元素
//快捷键: itit
while(iterator.hasNext()){
//next() 将指针下移,然后将下移以后集合位置上的元素返回
Object obj = iterator.next();
system.out.println("obj=" + obj);
}
3.2.2 增强for循环
//快捷键:I
System.out.println("增强for循环");
for (Object o :collection) {
System.out.println("o=" + o);
}
3.2.3 遍历例子
public class CollectionIterator {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection collection = new ArrayList();
collection.add(new Book("活着","余华",28.8));
collection.add(new Book("第七天","余华",20.8));
collection.add(new Book("平凡的时间","路遥",80.8));
//使用iterator实现遍历
//1、先得到collection的iterator
Iterator iterator = collection.iterator();
//2、使用while循环遍历
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//3、当遍历完成后iterator.next()指向最后一个元素,如果再用iterator.next()会报错NoSuchElementException
//4、要重新遍历可以重置迭代器
System.out.println("第二次迭代");
iterator = collection.iterator();
//快捷键 itit
//查看所有快捷键 Ctrl+j
System.out.println("迭代器遍历");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//快捷键 I
System.out.println("使用增强for循环遍历");
for (Object o : collection) {
System.out.println("o=" + o);
}
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public Book(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
4、List
4.1 List的特点
1)List中元素是有序的(即添加顺序和取出顺序一致)并且是可重复的。
2)List中的每个元素都有其对应的顺序索引,支持索引获取元素。
public class List_ {
@SuppressWarnings("all")
public static void main(String[] args) {
List list = new ArrayList();
//1、List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复[案例]
list.add("Tom");
list.add("Why");
list.add("Jack");
list.add("Mary");
list.add("Tom");
System.out.println("list="+list);
//2 、List集合中的每个元素都有其对应的顺序索引,即支持索引,索引是从0开始的
//3、List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
//4、JDK API中List接口的实现类有很多,常用的类有ArrayList、linkedList、vector
System.out.println(list.get(1));
}
}
4.2 List的常用方法和遍历
4.2.1 常用方法
public class ListMethod {
@SuppressWarnings("all")
public static void main(String[] args) {
List list = new ArrayList();
list.add("Tom");
list.add("Why");
list.add("Jack");
list.add("Mary");
list.add("Tom");
System.out.println("list="+list);
//list接口的常用方法,list集合里添加了一些根据索引来操作集合元素的方法
// 1) void add(int index, Object ele):在index位置插入ele元素
list.add(0,"cjj");
System.out.println("list="+list);
// 2)boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List list1 = new ArrayList();
list1.add("zcw");
list1.add(10);
list.addAll(0,list1);
System.out.println("list="+list);
//3 Object get(int index):获取指定index位置的元素
System.out.println(list.get(1));
//int indexOf(Object obj):返回obj在集合中首次出现的位置
System.out.println(list.indexOf("Tom"));
//5)int lastlndexOf(Object obj):返回obj在当前集合中末次出现的位置
System.out.println(list.lastIndexOf("Tom"));
//6)Object remove(int index):移除指定index位置的元素,并返回此元素
Object o = list.remove(7);
System.out.println("list="+list);
//7)Object set(int index, Object ele):设置指定index位置的元素为ele相当于是替换.索引必须存在,不然会报索引越界错误
list.set(0,"zy");
System.out.println("list="+list);
//8)List subList(int fromIndex, int tolndex):返回从fromlndex到tolndex位置的子集合
//左闭右开
System.out.println("list="+list.subList(0,1));
}
}
4.2.2 遍历
public class ListFor {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// List list = new ArrayList();
List list = new LinkedList();
// List list = new Vector();
list.add("Tom");
list.add("Jane");
list.add("Why");
list.add("Jery");
//迭代器循环
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
//增强for循环
for (Object o : list) {
System.out.println("o=" + o);
}
//普通for循环 fori
for (int i = 0; i < list.size(); i++) {
System.out.println("obj=" + list.get(i));
}
}
}
4.3 ArrayList的底层结构(数组)
1)ArrayList中维护了一个Object类型的数组elementData(transient Object[] elementData)
2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加数据时扩容为10,如需再次扩容,则扩容为elementData容量的1.5倍
3)如果使用指定大小的构造器,则elementData容量为指定大小,如果需要扩容,则直接扩容为elementData容量的1.5倍
@SuppressWarnings({"all"})
public class ArrayListSource {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList(9);
for (int i = 1; i <= 10 ; i++) {
arrayList.add(i);
}
for (int i = 11; i <= 15; i++) {
arrayList.add(i);
}
/*
使用无参构造方法ArrayList(),初始化elementData。
1. 执行ArrayList()构造方法,transient Object[] elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA(容量为0)。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. 执行add(E e),先确定是否需要扩容,扩容后再添加元素。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
3. 执行ensureCapacityInternal(int minCapacity),确定扩容后的容量,第一次扩容为10,第二次为15(10+10/2)。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
3.1 执行calculateCapacity(Object[] elementData, int minCapacity),确定elementData所需要的最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
3.2 执行ensureExplicitCapacity(int minCapacity),如果elementData的容量不足minCapacity(最小容量),则扩容至minCapacity。
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 记录修改次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3.3 执行grow(int minCapacity),真正的实现扩容。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//第二次及以后扩容为1.5倍(capacity+capacity右移一位)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//第一次扩容为10
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//扩容
}
使用有参构造方法ArrayList(int initialCapacity),初始化elementData。
1. 执行ArrayList(int initialCapacity)构造方法,设置elementData为new Object[initialCapacity]。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
如果要扩容就按照参数的1.5倍扩容,(initialCapacity+initialCapacity右移一位)。
*/
arrayList.add(100);
arrayList.add(200);
arrayList.add(null);
for (Object o : arrayList) {
System.out.println(o);
}
}
}
4.4 Vector的底层结构(数组)
1)Vector中维护了一个Object类型的数组elementData(protected Object[] elementData)
2)当创建Vector对象时,如果使用的是无参构造器,则初始elementData容量为10,如需扩容,则扩容为elementData容量的2倍
3)如果使用指定大小的构造器,则elementData容量为指定大小,如果需要扩容,则直接扩容为elementData容量的2倍
4)Vector是线程同步的,即线程安全,在开发中需要线程同步时,考虑使用Vector
@SuppressWarnings({"all"})
public class VectorSource {
public static void main(String[] args) {
Vector vector = new Vector(9);
for (int i = 1; i <= 10 ; i++) {
vector.add(i);
}
for (int i = 11; i <= 15; i++) {
vector.add(i);
}
/*
使用无参构造方法Vector(),创建和使用Vector
1. 执行Vector()
public Vector() {
this(10);
}
1.1 执行Vector(int initialCapacity)
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
1.2 执行Vector(int initialCapacity, int capacityIncrement),将elementData初始化,容量为10
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
2. 执行add(E e),确定elementData的容量,并将e添加到elementData中
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.1 执行ensureCapacityHelper(int minCapacity),确定elementData的容量
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
2.2 如果需要扩容则执行grow(int minCapacity),扩容elementData(原来容量的两倍),并将原有元素复制到新数组中
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
使用有参构造器Vector(int initialCapacity),创建和使用Vector
1. 执行Vector(int initialCapacity)
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
1.1 执行Vector(int initialCapacity, int capacityIncrement),将elementData初始化,容量为initialCapacity
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
如果要扩容就按照参数的2倍扩容,(initialCapacity+initialCapacity)。
*/
vector.add(100);
vector.add(200);
vector.add(null);
for (Object o : vector) {
System.out.println(o);
}
}
}
4.5 LinkedList的底层结构(双向链表)
1)LinkedList底层维护了一个双向链表
2)LinkedList中维护了两个属性first和last分别指向首节点和尾节点
3)每个节点(Node对象)里面又维护了prev、next、item三个属性,通过prev指向前一个节点,next指向后一个节点,这样来实现双向链表
4)LinkedList的删除和添加不是通过数组完成的,而是通过修改prev和next的指向完成,相对来说效率较高
@SuppressWarnings("all")
public class LinkedListMethod {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("LinkedList:" +linkedList);
/*
1、执行,这是linkedList的属性 first = null, last = null, size = 0
public LinkedList() {
}
2、执行 添加
public boolean add(E e) {
linkLast(e);
return true;
}
3、执行 将新的节点添加到链表最后
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
*/
//删除
linkedList.remove();
System.out.println("LinkedList:" + linkedList);
/*
1、执行 remove()默认调用removeFirst()方法
public E remove() {
return removeFirst();
}
2、执行 removeFirst()方法
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
3、执行 unlinkFirst(f) 方法
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
*/
//修改
linkedList.set(0,4);
System.out.println("LinkedList:" + linkedList);
//查看
System.out.println(linkedList.get(0));
//遍历
System.out.println("=======迭代器遍历========");
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
System.out.println("=======增强for循环遍历========");
for (Object o : linkedList) {
System.out.println(o);
}
System.out.println("=======普通for循环遍历========");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
}
}
4.6 ArrayList、Vector和LinkedList比较
1)如果改查的操作多,选择ArrayList
2)如果增删的操作多,选择LinkedList
3)如果有线程安全要求,选择Vector
4)一般来说在程序中80%-90%都是查询,大部分情况下会选择ArrayList
5)在一个项目中也可以根据业务灵活选择,一个模块使用ArrayList,另一个模块使用LinkedList等
5、Set
5.1 Set的特点
1)无序(添加和取出的顺序不一致),没有索引
2)不允许重复元素,所以最多包含一个null
@SuppressWarnings("all")
public class Set_ {
public static void main(String[] args) {
Set set = new HashSet();
//1)无序(添加和取出的顺序不一致),没有索引
set.add("apple");
set.add("banana");
set.add("orange");
set.add("tom");
System.out.println("set: " + set);
//2)不允许重复元素,所以最多包含一个null
set.add(null);
set.add(null);
set.add("apple");
System.out.println("set: " + set);
}
}
5.2 Set的常用方法和遍历
5.2.1 常用方法
5.2.2 遍历
public class SetFor {
public static void main(String[] args) {
Set set = new HashSet();
set.add("apple");
set.add("banana");
set.add(null);
System.out.println(set);
System.out.println("====迭代器===");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
System.out.println("====增强for循环===");
for (Object o : set) {
System.out.println(o);
}
}
}
5.3 HashSet的特点
1)HashSet实际上是HashMap
2)可以存放null值,但只能有一个null
3)HashSet不保证元素是有序的,取决于hashCode,再确定索引的结果(即不保证存放元素的顺序和取出顺序一致)
4)不能有重复的元素
@SuppressWarnings("all")
public class HashSet_ {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
System.out.println(hashSet.add("jack"));//T
System.out.println(hashSet.add("Tom"));//T
System.out.println(hashSet.add("Mary"));//T
System.out.println(hashSet.add("Jerry"));//T
//不能有重复的元素
System.out.println(hashSet.add("jack"));//F
hashSet.remove("jack");
System.out.println(hashSet);
hashSet.add("Joe");
hashSet.add("Ross");
hashSet.add("Joe");
hashSet.add(new Dog("Joe"));
hashSet.add(new Dog("Joe"));
System.out.println(hashSet);
}
}
class Dog{
private String name;
public Dog(String name) {
this.name = name;
}
public Dog(){
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
5.4 HashSet的底层结构(底层是HashMap(数组+链表+红黑树))
1)HashSet底层是HashMap
2)添加一个元素时先得到hash值,将hash值作为索引值
3)找到存储数据表table,看这个索引位置是否已经存放元素
4)如果没有直接加入
5)如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
6)当table的一条链表对应的节点到达了8个并且table的长度达到64就会转换为红黑树
@SuppressWarnings("all")
public class HashSetResource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("apple");
hashSet.add("banana");
hashSet.add("orange");
hashSet.add("java");
System.out.println(hashSet);
/*
1. 执行HashSet(),实际上是执行HashMap(),创建一个HashMap对象。
public HashSet() {
map = new HashMap<>();
}
1.1 执行HashMap(),设置加载因子为0.75(加载因子的作用是当HashMap中元素的个数大于等于阈值(加载因子乘以HashMap的容量)时,HashMap会自动扩容)。
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
2. 执行add(E e),将元素e添加到HashMap中。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
2.1 执行put() 计算key的hash值,然后将key-value对存放到数组的对应位置
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
2.1.1 执行 hash(key) 计算key的hash值,采用jdk1.8的hash算法,将key的hash值与该key的hash值无符号右移16位做异或操作,得到真正的hash值
public static int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.1.2 执行 putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) 存放key-value对到数组或链表中
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量,n为数组长度,i为数组下标,tab为数组,node为存放数据的节点
if ((tab = table) == null || (n = tab.length) == 0)// 数组为空,进行初始化
n = (tab = resize()).length;// 扩容,第一次扩容后,数组长度为默认值16
if ((p = tab[i = (n - 1) & hash]) == null)// 根据key的hash值得到要存放数据的位置,如果该位置为空,则直接存放
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//修改次数++
if (++size > threshold)
resize();
afterNodeInsertion(evict);// 节点插入后,调用afterNodeInsertion方法,进行一些后续操作,比如扩容,红黑树转换等
return null;
}
*/
}
}
5.5 HashSet小练习
1)定义一个Employee类,该类包含:private成员属性name,age 要求:创建3个Employee 放入 Hashset中;当 name和age的值相同时,认为是相同员工,不能添加到HashSet集合中
public class HashSetTest {
public static void main(String[] args) {
// 定义一个Employee类,该类包含:private成员属性name,age 要求:
//1.创建3个Employee 放入 Hashset中
//2.当 name和age的值相同时,认为是相同员工,不能添加到HashSet集合中
HashSet<Employee> employees = new HashSet<>();
employees.add(new Employee("Tmo",25));
employees.add(new Employee("Jerry",22));
employees.add(new Employee("Joe",24));
employees.add(new Employee("Joe",24));
System.out.println(employees);
}
}
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public Employee(){
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2)定义一个Employee类,该类包含:private成员属性name,sal,birthday(MyDate类型),其中 birthday 为 MyDate类型(属性包括:year, month, day),要求:创建3个Employee 放入 HashSet中;当 name和birthday的值相同时,认为是相同员工,不能添加到HashSet集合中
public class HashSetTest1 {
public static void main(String[] args) {
// 定义一个Employee类,该类包含:private成员属性name,sal,birthday(MyDate类
// 型),其中 birthday 为 MyDate类型(属性包括:year, month, day),要求:
// 1.创建3个Employee 放入 HashSet中
// 2.当 name和birthday的值相同时,认为是相同员工,不能添加到HashSet集合中
HashSet<Employee1> employee1s = new HashSet<>();
employee1s.add(new Employee1("Tom",5000,new MyData(2000,1,1)));
employee1s.add(new Employee1("Tom",5000,new MyData(2000,1,1)));
employee1s.add(new Employee1("Jerry",5000,new MyData(2000,1,1)));
System.out.println(employee1s);
}
}
class Employee1 {
private String name;
private double sal;
private MyData data;
public Employee1(String name, double sal, MyData data) {
this.name = name;
this.sal = sal;
this.data = data;
}
public Employee1() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyData getData() {
return data;
}
public void setData(MyData data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee1 employee1 = (Employee1) o;
return Objects.equals(name, employee1.name) && Objects.equals(data, employee1.data);
}
@Override
public int hashCode() {
return Objects.hash(name, data);
}
@Override
public String toString() {
return "Employee1{" +
"name='" + name + '\'' +
", sal=" + sal +
", data=" + data +
'}';
}
}
class MyData {
private int year;
private int month;
private int day;
public MyData(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public MyData() {
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyData myData = (MyData) o;
return year == myData.year && month == myData.month && day == myData.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
@Override
public String toString() {
return "MyData{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
5.6 LinkedHashSet(底层是LinkedHashMap,为数组+双向链表)
1)LinkedHashSet中维护了一个hash表和双向链表(有一个head和tail)
2)每一个节点有before和after属性,这样可以形成双向链表(可以保证遍历的时候读取顺序和插入顺序一致)
3)在添加一个元素时,先求hash值,再求索引,确定该元素在table的位置,然后将添加的元素加入双向链表(如果已经存在则不添加)
5.7 TreeSet(底层是TreeMap(红黑树))
1)不能插入null的值
2)元素不能重复
3)元素按照自然顺序或者根据提供的Comparator进行排序
4)自定义类要实现Comparable接口才能进行排序
@SuppressWarnings("all")
public class TreeSet_ {
public static void main(String[] args) {
// 自然排序
// TreeSet字符串按照长度排序,字符串长度相同按照字母顺序排序,字符串长度不同按照字符串顺序排序,null值排在最后
// int类型默认升序排序
TreeSet treeSet = new TreeSet();
//Comparator排序 自定义排序 按照字符串长度排序
TreeSet treeSet1 = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return o1.toString().length() - o2.toString().length();
}
});
/*
TreeSet底层是有一个TreeMap,TreeSet最大的特点就是提供了排序,可以按照自然排序或者自定义排序,自定义排序的话
可以用Comparator或者Comparable接口来实现自定义排序
它实现排序的原理就是当加入一个新元素时,会先获取传过去的自定义的比较器,再按照比较器里面比较规则进行排序,是通过一个
循环来完成的,当比较的结果为0时,就认为它们是重复的,TreeSet不会将第二个元素加入到集合中(因为treeSet是传入的key),而 TreeMap则会直接将该位置对应的value改变,就相当于替换。
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
*/
//Comparable排序 自定义排序 自定义类要实现Comparable接口再重写compareTo方法才能存储到TreeSet中
//如果两个元素通过比较器比较得出相等(返回 0),TreeSet 就认为它们是重复的,不会将第二个元素加入到集合中
TreeSet treeSet2 = new TreeSet();
treeSet2.add(new Person("Tom", 25));
treeSet2.add(new Person("Jerry", 30));
treeSet2.add(new Person("Mike", 20));
treeSet2.add(new Person("Lily", 25));
//自然排序 整型默认升序排序
treeSet.add(1);
treeSet.add(10);
treeSet.add(3);
treeSet.add(5);
treeSet.add(6);
treeSet.add(20);
treeSet1.add("abc");
treeSet1.add("d");
treeSet1.add("hi");
treeSet1.add("cdef");
//treeSet.add(null);// NullPointerException
System.out.println(treeSet);
System.out.println(treeSet1);
System.out.println(treeSet2);
}
}
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
6、Map
6.1 Map的特点
1)Map和Collection并列共存用于保存具有映射关系的数据:key-value
2)Map中的key不允许重复
3)Map中的value可以重复
4)Map中的key可以为null,value也可以为null,但key只能有一个为null,value可以有多个为null
5)常用String类作为Map的key
6)key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
public class Map_ {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1","why");
map.put("no1","zcw");//等价替换
map.put("no2","joe");
map.put("no3","joe");
map.put(null,null);
map.put("no4",null);
System.out.println(map);
//通过get获取key再返回对应的value
System.out.println(map.get("no4"));
}
}
6.2 Map的常用方法和遍历
6.2.1 常用方法
@SuppressWarnings("all")
public class MapMethod {
public static void main(String[] args) {
Map map = new HashMap();
//adding key-value pairs to the map
map.put("no1","Why");
map.put("no1","Joe");//this will replace the previous value
map.put("no3","Roos");//this will add a new key-value pair
map.put("no4","Tom");//this will add a new key-value pair/
map.put(null,"why");//this will not add a new key-value pair as the key is null
map.put("no5",null);//this will not add a new key-value pair as the value is null
System.out.println(map);
//根据key删除映射关系
map.remove(null);//removing a key-value pair from the map
System.out.println(map);
Object val = map.get("no1");//getting the value of a key
System.out.println(val);
//获取集合元素个数
System.out.println(map.size());//4
//判断集合是否为空
System.out.println(map.isEmpty());//F
//判断集合是否包含指定的key
System.out.println(map.containsKey("no1"));//T
//判断集合是否包含指定的value
System.out.println(map.containsValue("Joe"));//T
//清空集合
map.clear();
System.out.println(map);
}
}
6.2.2 遍历
@SuppressWarnings("all")
public class MapFor {
public static void main(String[] args) {
//创建Map对象
Map map = new HashMap();
map.put("no1","Java");
map.put("no2","Python");
map.put("no3","C++");
map.put("no4","Ruby");
map.put("no5","PHP");
//添加键值对
//方式一:keySet()方法,获取所有的key
Set keySet = map.keySet();
//(1) 增强for循环
System.out.println("增强for循环:");
for(Object key : keySet){
System.out.println(key + ":" + map.get(key));
}
//(2) 迭代器
System.out.println("迭代器:");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj + ":" + map.get(obj));
}
//方式二:values()方法,取出所有的value
Collection collection = map.values();
//(1) 增强for循环
System.out.println("========values()增强for循环:");
for(Object value : collection){
System.out.println(value);
}
//(2) 迭代器
System.out.println("=======values()迭代器:");
Iterator iterator2 = collection.iterator();
while (iterator2.hasNext()) {
Object obj = iterator2.next();
System.out.println(obj);
}
//方式三:entrySet()方法:获取所有k-v
Set entrySet = map.entrySet();
//(1) 增强for循环
System.out.println("=======entrySet()增强for循环:");
for(Object entry : entrySet){
Map.Entry me = (Map.Entry)entry;
System.out.println(me.getKey() + ":" + me.getValue());
}
//(2) 迭代器
System.out.println("=======entrySet()迭代器:");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
//iterator3.next() 返回的是HashMap$Node对象, HashMap$Node->实现了Map.Entry接口
// 所以可以强转,再使用getKey()和getValue()方法
Map.Entry me = (Map.Entry)iterator3.next();
System.out.println(me.getKey() + ":" + me.getValue());
}
}
}
6.4 Map的小练习
使用HashMap添加3个员工对象,要求:键:员工id,值:员工对象,并遍历显示工资>18000的员工(遍历方式至少两种);员工类:姓名、工资、员工id
public class MapTest {
public static void main(String[] args) {
//使用HashMap添加3个员工对象,要求
//键:员工id
//值:员工对象
//并遍历显示工资>18000的员工(遍历方式最少两种)
//员工类:姓名、工资、员工id
Map map = new HashMap();
map.put(1,new Employee("Tom", 18500, 1));
map.put(2,new Employee("Jerry", 20000, 2));
map.put(3,new Employee("Mike", 17000, 3));
Set keySet = map.keySet();
System.out.println("keySet: 增强for循环");
for (Object o : keySet) {
Employee employee = (Employee) map.get(o);
// System.out.println(o + ":" + map.get(o));
if (employee.getSalary() > 18000){
System.out.println(employee.toString());
}
}
Set entrySet = map.entrySet();
System.out.println("entrySet: 增强for循环");
for (Object o : entrySet) {
Map.Entry entry = (Map.Entry) o;
// System.out.println(entry.getKey() + ":" + entry.getValue());
Employee employee = (Employee) entry.getValue();
if (employee.getSalary() > 18000){
System.out.println(employee.toString());
}
}
}
}
class Employee {
private String name;
private double salary;
private int id;
public Employee(String name, double salary, int id) {
this.name = name;
this.salary = salary;
this.id = id;
}
public Employee() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", id=" + id +
'}';
}
}
6.5 HashMap的特点
1)HashMap是以k-v的方式来存放元素
2)key不能重复,但是value可以重复,允许使用null键和null值
3)如果添加相同的key,则会覆盖原来的k-v,等同于修改(k不会被替换,v会被替换)
4)不保证k-v存储是有序的(存储位置取决于hashCode)
5)没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
6.6 HashMap的底层结构(数组+链表+红黑树)
1)HashMap底层维护了Node类型的数组table,默认为null
2)当创建对象时,将加载因子(loadfactor)初始化为0.75
3)当添加key-val时,通过key的哈希值得到在tablel的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的key
是否相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相
应处理。如果添加时发现容量不够,则需要扩容。
4)第1次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)
5)以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,依次类推
6)在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且
tablel的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
@SuppressWarnings("all")
public class HashMapResource {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("java", 10);
hashMap.put("c++", 15);
hashMap.put("python", 20);
hashMap.put("javascript", 25);
// 底层机制:数组+链表+红黑树
// 数组:存放key-value对,通过hash算法计算key的hash值,然后将key-value对存放到数组的对应位置
// 链表:当hash冲突时,将key-value对存放到链表中
// 红黑树:当链表长度大于8时并且数组长度大于64时,链表转换为红黑树,提高查询效率
// 扩容:当存放进hash表的数据到达临界值时,进行扩容,扩容后重新计算hash值,将key-value对存放到新的数组中
// 底层源码分析
/*
1. 执行HashMap() 将加载因子设置为默认值0.75(用hash表的长度*加载因子得到一个临界值,当hash表的元素个数超过临界值时,进行扩容)
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
2. 执行put() 计算key的hash值,然后将key-value对存放到数组的对应位置
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
2.1 执行 hash(key) 计算key的hash值,采用jdk1.8的hash算法,将key的hash值与该key的hash值无符号右移16位做异或操作,得到真正的hash值
public static int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.2 执行 putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) 存放key-value对到数组或链表中
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量,n为数组长度,i为数组下标,tab为数组,node为存放数据的节点
if ((tab = table) == null || (n = tab.length) == 0)// 数组为空,进行初始化
n = (tab = resize()).length;// 扩容,第一次扩容后,数组长度为默认值16
if ((p = tab[i = (n - 1) & hash]) == null)// 根据key的hash值得到要存放数据的位置,如果该位置为空,则直接存放
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//修改次数++
if (++size > threshold)
resize();
afterNodeInsertion(evict);// 节点插入后,调用afterNodeInsertion方法,进行一些后续操作,比如扩容,红黑树转换等
return null;
}
*/
for (int i = 0; i <= 12 ; i++) {
hashMap.put(new Book(i), "book");
}
System.out.println(hashMap);
}
}
class Book {
private int id;
public Book(int id) {
this.id = id;
}
public Book() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public int hashCode() {
return 100;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
'}';
}
}
6.7 Hashtable的特点(数组+链表)
1)存放的元素是k-v
2)key和value都不能为null,否则会抛出NullPointerException
3)是线程安全的(方法前有synchronized)
@SuppressWarnings("all")
public class HashTable_ {
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();
hashtable.put("apple", 1);
hashtable.put("banana", 2);
hashtable.put("orange", 3);
hashtable.put(null, 4);//NullPointerException
hashtable.put("apples", null);//NullPointerException
System.out.println(hashtable);
}
}
6.8 Properties的特点和常用方法
1)Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
2)它的使用特点和Hashtable类似
3)Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
public class Properties_ {
public static void main(String[] args) {
//properties的特点;
//Properties继承自Hashtable,数据以k-v形式存储
Properties properties = new Properties();
properties.put("name", "John");
properties.put("age", "30");
properties.put("city", "New York");
// properties.put(null,"null value");key为null会抛出NullPointerException
// properties.put("data",null); value为空会抛出NullPointerException
properties.put("city", "Beijing");//如果key相同则value会被替换
System.out.println(properties);
//通过key获取value
System.out.println(properties.get("name"));
//properties的方法
//删除
properties.remove("name");
System.out.println(properties);
//修改
properties.setProperty("name", "Tom");
System.out.println(properties);
//查找
System.out.println(properties.get("name"));
}
}
6.9 TreeMap(红黑树)
1)TreeMap是用键进行排序的,默认采用升序排序;通过Comparable或Comparator来排序;
2)允许值重复,不允许键重复;
3)键不可以为null,值可以为null;
@SuppressWarnings("all")
public class TreeMap_ {
public static void main(String[] args) {
//自然排序,按照key排序,int类型默认升序,字符串类型按照字符串长度升序
TreeMap treeMap = new TreeMap();
//key不能重复,value可以重复,key重复时,后面的会覆盖前面的
treeMap.put(1, "a");
treeMap.put(3, "c");
treeMap.put(2, "b");
treeMap.put(6, "a");
// treeMap.put(null, "d");//NullPointerException
//value可以为null
treeMap.put(4, null);
System.out.println(treeMap);
//自然排序,按照key排序,字符串类型按照字符串长度升序
TreeMap treeMap1 = new TreeMap();
treeMap1.put("abc", "a");
treeMap1.put("abcd", "b");
treeMap1.put("ab", "c");
System.out.println(treeMap1);
//Comparator自定义排序
TreeMap treeMap2 = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o2).length() -((String)o1).length();
}
});
treeMap2.put("abcde", "a");
treeMap2.put("abcd", "b");
treeMap2.put("abc", "c");
System.out.println(treeMap2);
//Comparable排序 实现Comparable接口,重写compareTo方法,按照age排序
TreeMap treeMap3 = new TreeMap();
treeMap3.put(new Student(1,"a",20),"a");
treeMap3.put(new Student(3,"b",25),"b");
treeMap3.put(new Student(2,"c",18),"c");
System.out.println(treeMap3);
}
}
class Student implements Comparable<Student> {
private int id;
private String name;
private int age;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.id - o.id;
}
}
7、泛型
1)类型参数:泛型允许在类、接口和方法中使用类型参数。常见的类型参数有 T
(Type)、E
(Element)、K
(Key)、V
(Value)等
2)类型安全:泛型在编译时进行类型检查,避免了运行时的 ClassCastException
3)代码重用:通过泛型,可以编写与类型无关的代码,增强代码的重用性
泛型可分为泛型类、泛型接口、泛型方法
7.1 泛型类
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
7.2 泛型接口
public interface Container<T> {
void add(T item);
T get(int index);
}
7.3 泛型方法
public class Util {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
7.4 通配符(用于表示不确定的类型)
无界通配符:<?>
表示任意类型
public void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
上界通配符:<? extends T>
表示类型是 T
或 T
的子类
public void process(List<? extends Number> list) {
// 可以处理 Number 或其子类
}
下界通配符:<? super T>
表示类型是 T
或 T
的父类
public void addNumbers(List<? super Integer> list) {
// 可以添加 Integer 或其子类
}
最后示例(其实泛型在集合中就是起一个定义参数类型的作用)
public class Test {
public static void main(String[] args) {
ArrayList<Teacher> arrayList = new ArrayList<>();
arrayList.add(new Teacher("John", 25));
arrayList.add(new Teacher("Mary", 30));
arrayList.add(new Teacher("Tom", 35));
System.out.println(arrayList);
}
}
class Teacher {
private String name;
private int age;
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public Teacher() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
8、总结-开发中如何选择集合实现类
1)先判断存储的类型(一组对象[单列]或一组键值对[双列])
2)一组对象[单列]:collection接口
2.1)允许重复:List
增删多:LinkedList(底层维护了一个双向链表)
改查多:ArrayList(底层维护了一个数组)
2.2)不允许重复:Set
无序:HashSet
排序:TreeSet
插入和取出顺序一致:LinkedHashSet(数组+双向链表)
3)一组键值对[双列]:Map
键无序:HashMap
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取xxx.properties文件:Properties