集合框架
1、集合的概念
概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。
和数组区别:
- 数组长度固定,集合场长度不固定。
- 数组可以存储基本类型和引用类型,集合只能存储引用类型
位置:java.util.*;
2、Collection接口
Collection父接口:
特点:代表一组任意类型的对象,无序、无下标、不能重复。
Collection的使用:
package com.xu;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.function.DoubleToIntFunction;
/*
* 演示Collection接口:
* 1、添加元素
* 2、删除元素
* 3、遍历元素
* 4、判断
* */
public class Demo1 {
public static void main(String[] args) {
//创建Collection
Collection collection=new ArrayList();
//1、添加元素
collection.add("苹果");
collection.add("香蕉");
collection.add("橘子");
System.out.println("添加元素---------------------------");
System.out.println(collection);
//2、删除元素
// collection.remove("苹果");
// collection.clear();//清空
// System.out.println(collection);
//3、遍历:增强for
System.out.println("增强for---------------------------");
for (Object o : collection) {
System.out.println(o);
}
System.out.println("迭代器-----------------------------");
//遍历:迭代器(专门遍历集合的一种方式)在迭代的过程中,不可以使用Collection的remove方法,否则就会报并发修改异常
//hasNext():判断有没有下一个元素
//next():获取下一个元素
//remove():删除当前元素 使用remove()将迭代器新返回的元素删除。必须先执行next方法。
Iterator iterator = collection.iterator();
while(iterator.hasNext()){
String next = (String)iterator.next();
System.out.println(next);
iterator.remove();
}
System.out.println(collection.size());
//4、判断
System.out.println("判断是否有某个元素-----------------------------------");
System.out.println(collection.contains("苹果"));//判断是否有某个元素
System.out.println("判断是否为空----------------------------------------");
System.out.println(collection.isEmpty());//判断是否为空。
}
}
package com.xu;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/*
* Collection的使用:保存学生信息
*/
public class Demo2 {
public static void main(String[] args) {
//新建Collection
Collection collection=new ArrayList();
//实例化
Student student1 = new Student("四代目火影", 23);
Student student2 = new Student("六代目火影", 23);
Student student3 = new Student("七代目火影", 23);
//1、添加:添加的只是对象地址
collection.add(student1);
collection.add(student2);
collection.add(student3);
System.out.println("添加学生-------------------------------");
System.out.println(collection.size());
System.out.println(collection.toString());
//删除
//collection.remove(student1);
//collection.clear();清除 也只是集合里面的地址被清除了
collection.remove(new Student("七代目火影", 23).hashCode());//这个方法不行 原因是新创建的这个对象与collection中的对象无关,也只是属性一样而已
System.out.println(collection.size());
//遍历:增强for
System.out.println("for学生-------------------------------");
for (Object o : collection) {
Student student = (Student) o;
System.out.println(student.toString());
}
//遍历:iterator
System.out.println("iterator学生-------------------------------");
Iterator iterator = collection.iterator();
while(iterator.hasNext()){
Student student5 =(Student) iterator.next();
System.out.println(student5.toString());
}
//4、判断
System.out.println(collection.contains(student2));
System.out.println(collection.isEmpty());
}
}
3、List接口与实现类
List子接口:特点:有序(添加的顺序和遍历的顺序是一样的)、有下标、元素可以重复。
package com.xu;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/*
* list子接口特点:有序、有下标、可重复
*/
public class Demo3 {
public static void main(String[] args) {
//创建一个集合
List list=new ArrayList<>();
//1、添加元素
list.add("苹果1");
list.add("苹果2");
list.add("苹果3");
list.add("苹果3");
list.add(0,"乔布斯");//按照下表添加
System.out.println("添加-----------------------------------");
System.out.println(list);
System.out.println(list.size());
//2、删除元素
System.out.println("删除-----------------------------------");
list.remove(2);//按照下标删除
list.remove("苹果1");
System.out.println(list);
System.out.println(list.size());
//3、遍历:for
System.out.println("遍历:for-----------------------------------");
for (int i = 0; i <list.size(); i++) {
System.out.println((String)list.get(i));
}
//遍历:增强for
System.out.println("遍历:增强for-----------------------------------");
for (Object o : list) {
System.out.println((String)o);
}
//遍历:迭代器
System.out.println("遍历:迭代器-----------------------------------");
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//遍历:列表迭代器 :可以任意方向迭代,还可以删除、添加、更改
System.out.println("遍历:列表迭代器-----------------------------------");
ListIterator listIterator = list.listIterator();
listIterator.add("苹果4");//列表:表头插入、表尾删除
while(listIterator.hasNext()){//从前往后
System.out.println(listIterator.nextIndex()+":"+listIterator.next());
}
System.out.println("遍历:列表迭代器-----------------------------------");
while(listIterator.hasPrevious()){//从后往前 先把指针移到最后
System.out.println(listIterator.previousIndex()+":"+listIterator.previous());
}
System.out.println("遍历:列表迭代器-----------------------------------");
while(listIterator.hasNext()){//从前往后
System.out.println(listIterator.nextIndex()+":"+listIterator.next());
listIterator.set("徐冲无敌最俊朗");
}
System.out.println(list);
//4、判断
System.out.println(list.contains("乔布斯"));//false
System.out.println(list.isEmpty());//false
//5、获取位置
System.out.println(list.indexOf("徐冲无敌最俊朗"));//获取位置时,若有重复,获取第一个数值的位置 0
}
}
package com.xu;
import java.util.ArrayList;
import java.util.List;
/*
* list的使用:数字的使用
*/
public class Demo4 {
public static void main(String[] args) {
List list=new ArrayList();
//自动装箱
list.add(20);
list.add(30);
list.add(40);
list.add(50);
list.add(60);
System.out.println("元素个数:"+list.size());
System.out.println(list.toString());
//2、删除
//list.remove(0);下标
list.remove(new Integer(20));
System.out.println("元素个数:"+list.size());
System.out.println(list.toString());
//补充方法:取子集
List list1 = list.subList(1, 3);//包含1 不包含3
System.out.println("取子集:");
System.out.println(list1);
}
}
ArrayList类:
- 数组结构实现,查询快、增删慢;数组是连续的
- JDK1.2版本,运行效率快,线程不安全
- clone克隆 要实现接口Cloneable,这是一个标记接口,自身没有方法。 覆盖clone()方法,可见性提升为public
package com.xu;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
/*
* ArrayList的使用
* 存储结构:数组 查找遍历速度快 增删慢
*/
public class Demo5 {
public static void main(String[] args) {
ArrayList<Object> arrayList = new ArrayList<>();//默认容量为0
//1、添加元素
Student student1 = new Student("波风水门", 23);
Student student2 = new Student("卡卡西", 23);
Student student3 = new Student("鸣人", 23);
arrayList.add(student1);
arrayList.add(student2);
arrayList.add(student3);
System.out.println("添加元素-----------------------------");
System.out.println("元素个数:"+arrayList.size());
System.out.println(arrayList.toString());
//2、删除元素
System.out.println("删除元素-----------------------------");
arrayList.remove(new Student("鸣人", 23));//equals比较的是地址中的内容 重写equals方法
System.out.println("元素个数:"+arrayList.size());
System.out.println(arrayList.toString());
//3、遍历元素(重点)
System.out.println("简单迭代器:遍历元素-----------------------------");
//简单迭代器
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
System.out.println(((Student)iterator.next()).toString());
}
System.out.println("列表迭代器:遍历元素-----------------------------");
ListIterator<Object> listIterator = arrayList.listIterator();
while(listIterator.hasNext()){
System.out.println(listIterator.nextIndex()+":"+listIterator.next());
}
System.out.println("列表迭代器:遍历元素-----------------------------");
while(listIterator.hasPrevious()){
System.out.println(listIterator.previousIndex()+":"+listIterator.previous());
}
//4、判断
System.out.println("判断-----------------------------");
System.out.println(arrayList.contains(student3));
System.out.println(arrayList.isEmpty());
//5、查找
System.out.println("查找-----------------------------");
System.out.println(arrayList.indexOf(student1));
}
}
//重写equals
@Override
public boolean equals(Object o) {
//1、判断当前对象和传递过来的是否为同一个
if (this == o) return true;
//判断是否为空
if(o==null) return false;
//2、判断是否为同一个类
if (!(o instanceof Student)) return false;
Student student = (Student) o;
//3、比较属性
return getAge() == student.getAge() &&
Objects.equals(getName(), student.getName());
}
源码分析:
private static final int DEFAULT_CAPACITY = 10;//默认容量大小 添加元素之后的大小
/*注意:如果没有元素添加,默认容量大小为0。*/
transient Object[] elementData;//存放元素的数组
private int size;//实际的元素个数
/*扩容:每次扩容原来的1.5倍个元素。*/
add方法 添加元素:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 增加修改个数
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
Vector类:
- 数组结构实现,查询快、增删慢;数组是连续的
- JDK1.0版本,运行效率慢,线程安全
package com.xu;
import java.util.Enumeration;
import java.util.Vector;
/*Vector集合的使用*/
public class Demo6 {
public static void main(String[] args) {
//创建集合
Vector vector=new Vector();
//1、添加元素
vector.add("草莓");
vector.add("香蕉");
vector.add("西瓜");
System.out.println("添加-------------------------");
System.out.println("元素个数:"+vector.size());
System.out.println(vector.toString());
//2、遍历
//使用枚举器
System.out.println("使用枚举器-------------------------");
Enumeration elements = vector.elements();
while(elements.hasMoreElements()){
System.out.println((String)elements.nextElement());
}
//3、判断
System.out.println("判断-------------------------");
System.out.println(vector.contains("火警"));
System.out.println(vector.isEmpty());
//4、补充方法
System.out.println("补充方法-------------------------");
System.out.println(vector.firstElement());//首元素
System.out.println(vector.lastElement());//尾元素
}
}
LinkedList类:
- 双向链表结构实现,查询慢、增删快;链表是不连续的
- 运行效率快,线程不安全
package com.xu;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
/*
* LinkedList的使用
* 存储结构:双向链表*/
public class Demo7 {
public static void main(String[] args) {
//创建集合
LinkedList linkedList=new LinkedList();
//1、添加信息
Student student1 = new Student("飞剑", 23);
Student student2 = new Student("预见", 23);
Student student3 = new Student("挥剑", 23);
linkedList.add(student1);
linkedList.add(student2);
linkedList.add(student3);
System.out.println("添加信息--------------------------------------");
System.out.println("元素个数:"+linkedList.size());
System.out.println(linkedList.toString());
//2、删除信息
linkedList.remove(0);
System.out.println("删除信息--------------------------------------");
System.out.println("元素个数:"+linkedList.size());
System.out.println(linkedList.toString());
//3、遍历:for
System.out.println("遍历:for--------------------------------------");
for (int i = 0; i < linkedList.size() ; i++) {
System.out.println(linkedList.get(i));
}
//增强for
System.out.println("增强for--------------------------------------");
for (Object o : linkedList) {
System.out.println((Student)o);
}
//迭代器
System.out.println("迭代器--------------------------------------");
Iterator iterator = linkedList.iterator();
while(iterator.hasNext()){
System.out.println((Student)iterator.next());
}
//链表迭代器
System.out.println("链表迭代器--------------------------------------");
ListIterator listIterator = linkedList.listIterator();
while(listIterator.hasNext()){
System.out.println(listIterator.nextIndex()+":"+listIterator.next());
}
System.out.println("链表迭代器--------------------------------------");
while(listIterator.hasPrevious()){
System.out.println(listIterator.previousIndex()+":"+listIterator.previous());
}
//4、判断
System.out.println("判断--------------------------------------");
System.out.println(linkedList.contains(student2));
System.out.println(linkedList.isEmpty());
//5、获取
System.out.println("获取--------------------------------------");
System.out.println(linkedList.indexOf(student3));
}
}
源码分析:
transient int size = 0;//元素个数
transient Node<E> first;//首节点 为空
transient Node<E> last;//尾节点 为空
add方法:
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;//prev :空
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
private static class Node<E> {
E item;/实际数据
Node<E> next;//下个节点
Node<E> prev;//上个节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
ArrayList类和LinkedList类的区别:
- ArrayList类:必须开辟连续空间,查询快,增删慢
- LinkedList类:无需开辟连续空间,查询慢,增删快
4、泛型和工具类
- Java是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型当做参数传递。
- 常见的形式有泛型类、泛型接口、泛型方法。
- 语法:<T,…>T称为类型占位符,表示一种引用类型
- 好处:提高代码的重用性。防止类型转换异常,提高代码的安全性。
泛型类
package com.xu;
/*泛型类
* 语法:类名的后面加<T,T...>
* T是类型占位符,表示一种引用类型,如果编写多个用,隔开
* */
public class MyGeneric<T> {
//使用泛型T
//1、创建变量 不能实例化 因为不知道传过来的参数的构造函数是否可以用
T t;
//2、添加方法
public void show(T t){
System.out.println(t);
}
//3、使用泛型作为函数返回值
public T getT(){
return t;
}
}
package com.xu;
/*使用泛型类*/
public class Test {
public static void main(String[] args) {
//使用泛型类创建对象 不同的泛型类型不能相互赋值
MyGeneric<String> myGeneric=new MyGeneric<>();//后面的<>可写可不写 JDK1.7以前要写。
myGeneric.t="hello";
myGeneric.show("大家");
String s = myGeneric.getT();
System.out.println(s);
MyGeneric<Integer> myGeneric1=new MyGeneric<>();
myGeneric1.t=100;
myGeneric1.show(1);
Integer integer = myGeneric1.getT();
System.out.println(integer);
}
}
泛型接口
package com.xu;
/*泛型接口
* 接口名后加<T>
* */
public interface MyInterface<T> {
String name="张";
T server(T t);
}
package com.xu;
/*接口实现类 创建接口类就能确定泛型类型*/
public class MyInterfaceImpl implements MyInterface<String> {
@Override
public String server(String s) {
System.out.println(s);
return s;
}
}
package com.xu;
/*接口实现类 创建接口类不能确定泛型类型*/
public class MyInterfaceImpl1<T> implements MyInterface<T> {
@Override
public T server(T t) {
System.out.println(t);
return t;
}
}
package com.xu;
/*使用泛型类*/
public class Test {
public static void main(String[] args) {
MyInterfaceImpl myInterface = new MyInterfaceImpl();
myInterface.server("hello");
MyInterfaceImpl1<Integer> myInterfaceImpl1=new MyInterfaceImpl1<>();
myInterfaceImpl1.server(100);
}
}
泛型方法
package com.xu;
/*
* 泛型方法
* 语法:<T> 返回值类型
* */
public class MyGenericMethod {
//泛型方法
public <T> T show( T t){
System.out.println("泛型方法"+t);
return t;
}
}
package com.xu;
/*使用泛型类*/
public class Test {
public static void main(String[] args) {
MyGenericMethod myGenericMethod = new MyGenericMethod();
myGenericMethod.show(10000);
MyGenericMethod myGenericMethod1 = new MyGenericMethod();
myGenericMethod1.show("中国加油");
}
}
泛型好处
- 提高代码的重用性。
- 防止类型转换异常,提高代码的安全性。
泛型集合
- 概念:参数化类型、类型安全的集合,强制集合元素的类型一致。
- 特点:编译时即可检查,而非运行时抛出异常。
- 访问时,不必类型转换(拆箱)。
- 不同类型之间引用不能相互赋值,泛型不存在多态。
package com.xu;
import java.util.ArrayList;
public class Demo8 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("xxx");
arrayList.add("yyy");
arrayList.add(10);
arrayList.add(20);
for (Object o : arrayList) {
String s=(String)o;
System.out.println(s);
}
}
}
[
解决这个类型转化问题:
会自动报错。
package com.xu;
import java.util.ArrayList;
public class Demo8 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList();
arrayList.add("xxx");
arrayList.add("yyy");
for (String s : arrayList) {
System.out.println(s);
}
}
}
5、Set接口与实现类
Set特点:无序、无下标、元素不可重复。
方法:全部继承自Collection中的方法
package com.xu;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/*set接口的使用*/
public class DemoSetInterface {
public static void main(String[] args) {
//创建集合
Set<String> set =new HashSet<>();
//1、添加元素
System.out.println("添加元素--------------------------");
set.add("火");
set.add("水");
set.add("土");
set.add("金");
set.add("木");
System.out.println("元素个数:"+set.size());//无序、不可重复。
System.out.println(set.toString());
//2、删除元素
System.out.println("删除元素--------------------------");
set.remove("火");
System.out.println("元素个数:"+set.size());//无序、不可重复。
System.out.println(set.toString());
//3、遍历:增强for
System.out.println("遍历:增强for--------------------------");
for (String s : set) {
System.out.println(s);
}
//4、迭代器
System.out.println("迭代器--------------------------");
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//5、判断
System.out.println("判断--------------------------");
System.out.println(set.contains("水"));
System.out.println(set.isEmpty());
}
}
HashSet(重点)使用
HashSet实现了Set接口,它不允许集合中有重复的值,当我们提到HashSet时,第一件事情就是在将对象存储在HashSet之前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。如果我们没有重写这两个方法,将会使用这个方法的默认实现。
public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加的话会返回true。
- 基于HashCode计算元素存放的位置。
- 当存入元素的哈希码相同时,会调用equals进行确认,若结果为true,则拒绝后者存入。
哈希表:
package com.xu;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/*HashSet的使用
* 存储结构是:哈希表(数组+链表+红黑树)JDK1.8以后
* */
public class Demo9 {
public static void main(String[] args) {
//新建集合
HashSet<String> set = new HashSet<>();
//1、添加元素
set.add("炎龙");
set.add("风影");
set.add("黑犀");
set.add("地虎");
set.add("雪獒");
set.add("界王");
System.out.println("添加元素--------------------------------");
System.out.println("元素个数:"+set.size());//无序、不重复的、无下标
System.out.println(set.toString());
//2、删除元素
set.remove("界王");
System.out.println("删除元素--------------------------------");
System.out.println("元素个数:"+set.size());//无序、不重复的、无下标
System.out.println(set.toString());
//3、遍历:增强for
System.out.println("遍历:增强for--------------------------------");
for (String s : set) {
System.out.println(s);
}
//迭代器
System.out.println("迭代器--------------------------------");
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//4、判断
System.out.println(set.contains("界王"));
System.out.println(set.isEmpty());
}
}
HashSet存储方式
package com.xu;
import java.util.HashSet;
/*HashSet的使用(2)
* 存储过程:
* 1、利用HashCode计算保存位置 如果此位置为空,可以储存,如果不为空。则不能。
* 2、利用equals方法,如果结果为true。认为该元素重复,否则形成链表
* */
public class Demo10 {
public static void main(String[] args) {
//创建集合
HashSet<Student> set = new HashSet<>();
//1、添加信息
Student s1=new Student("炎龙",23);
Student s2=new Student("风影",23);
Student s3=new Student("黑犀",23);
Student s4=new Student("地虎",23);
Student s5=new Student("雪獒",23);
set.add(s1);//重复不能添加
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
set.add(new Student("炎龙",23));//不重复:重写hashCode方法和equals方法
System.out.println("添加信息-----------------------");
System.out.println("元素个数:"+set.size());
System.out.println(set.toString());
//2、删除信息
set.remove(s1);
System.out.println("删除信息-----------------------");
System.out.println("元素个数:"+set.size());
System.out.println(set.toString());
}
}
HashSet补充
- 为什么重新HashCode会出现fianl int prim=31;
- 31是质数。尽量减少散列冲突。
- 可以提高执行的效率 31*i=i(<<5)-1;左移五位再减一。
TreeSet概述
- 基于排列顺序实现元素不重复。
- 实现了SortedSet接口,对集合元素自动排序。
- 元素对象的类型必须实现Comparable接口,指定排序规则。
- 通过CompareTo方法确定为是否为重复元素。
存储结构:红黑树:在二叉查找树上面添加颜色。平衡,查找简单。
TreeSet使用
package com.xu;
import java.util.Iterator;
import java.util.TreeSet;
/*TreeSet的使用
* 存储结构:红黑树
* */
public class Demo11 {
public static void main(String[] args) {
//1、创建集合
TreeSet<String> treeSet = new TreeSet<>();
//1、添加元素
treeSet.add("火影");
treeSet.add("风影");
treeSet.add("水影");
treeSet.add("雷影");
treeSet.add("土影");
treeSet.add("暗影");
System.out.println("添加元素--------------------------------------");
System.out.println("元素个数:"+treeSet.size());
System.out.println(treeSet.toString());
//2、删除元素
treeSet.remove("暗影");
System.out.println("删除元素--------------------------------------");
System.out.println("元素个数:"+treeSet.size());
System.out.println(treeSet.toString());
//3、遍历:增强for
System.out.println("遍历:增强for--------------------------------------");
for (String s : treeSet) {
System.out.println(s);
}
//迭代器
System.out.println("迭代器--------------------------------------");
Iterator<String> iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//4、判断
System.out.println("判断--------------------------------------");
System.out.println(treeSet.contains("火影"));
System.out.println(treeSet.isEmpty());
}
}
实例添加:
原因:没有比较标准。
解决方案:实例实现Comparable接口中的CompareTo方法。
package com.xu;
import java.util.TreeSet;
public class Demo12 {
public static void main(String[] args) {
//创建对象
TreeSet<Student> students = new TreeSet<>();
//1、添加元素
Student s1=new Student("火影",23);
Student s2=new Student("风影",23);
Student s3=new Student("水影",23);
Student s4=new Student("土影",23);
Student s5=new Student("雷影",23);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
System.out.println("添加元素+++++++++++++++++++++++++++++");
System.out.println("元素个数:"+students.size());
System.out.println(students.toString());
}
}
//compareTo方法返回值为0,认为为重复元素。
public class Student implements Comparable<Student>{
@Override
public int compareTo(Student o) {
int n1=this.getName().compareTo(o.getName());
int n2=this.getAge()-o.getAge();
return n1==0?n2:n1;
}
}
Comparator接口
package com.xu;
import java.util.Comparator;
import java.util.TreeSet;
/*
* TreeSet集合使用
* Comparator:实现定制比较(比较器)。
* */
public class Demo23 {
public static void main(String[] args) {
//创建集合
TreeSet<Student> students=new TreeSet<>(new Comparator<Student>() {//匿名内部类
@Override
public int compare(Student o1, Student o2) {
int n1=o1.getAge()-o2.getAge();
int n2=o1.getName().compareTo(o2.getName());
return n1==0?n2:n1;
}
});
//1、添加元素
Student s1=new Student("影",23);
Student s2=new Student("风",23);
Student s3=new Student("水",23);
Student s4=new Student("土",23);
Student s5=new Student("雷",23);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
System.out.println("添加元素+++++++++++++++++++++++++++++");
System.out.println("元素个数:"+students.size());
System.out.println(students.toString());
}
}
TreeSet案例
package com.xu;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
/*TreeSet案例:
* 要求:按照TreeSet集合中字符串的长度排列
* */
public class Demo14 {
public static void main(String[] args) {
TreeSet<String> strings = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int n1=o1.length()-o2.length();
int n2=o1.compareTo(o2);
return n1==0?n2:n1;
}
});
strings.add("fdasfas");
strings.add("das");
strings.add("虎牙");
System.out.println(strings);
}
}
6、Map接口与实现类
Map集合概述
Map接口特点:
- 用于存储任意键值对(Key-Value)
- 键:无序、无下标、不允许重复(唯一)
- 值:无序、无下标、允许重复
Map接口使用
package com.xu.Map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*Map接口的使用:
* 特点:存储任意个键值对 键:无序、无下标、不可重复 值:无序、无下标、可重复。
* */
public class map {
public static void main(String[] args) {
//创建集合
Map<String, String> map = new HashMap<>();
//1、添加元素
map.put("cn","中国");
map.put("usa","美国");
map.put("uk","英国");
System.out.println("添加元素---------------------------");
System.out.println("元素个数:"+map.size());//键:无序、无下标、不可重复 值:如果添加和Key对应的值一样,则会被抵消掉。
System.out.println(map.toString());
//2、删除元素
map.remove("us");
System.out.println("删除元素---------------------------");
System.out.println("元素个数:"+map.size());//键:无序、无下标、不可重复 值:如果添加和Key对应的值一样,则会被抵消掉。
System.out.println(map.toString());
//3、遍历:使用keySet()方法:返回的是所有Key的Set集合
System.out.println("使用keySet()方法---------------------------");
Set<String> set = map.keySet();
for (String s : set) {
System.out.println(s+":"+map.get(s));
}
//遍历:使用entrySet()方法:返回的是所有的映射对Entry<String, String> 效率比KeySet()高
System.out.println("使用entrySet()方法---------------------------");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
//4、判断
System.out.println("判断---------------------------");
System.out.println(map.containsKey("usa"));
System.out.println(map.containsValue("中国"));
System.out.println(map.isEmpty());
}
}
HashMap使用
HashMap:
HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap允许键和值为null。HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。
public Object put(Object Key,Object value)方法用来将元素添加到map中。
- JDK1.2版本,线程不安全,运行效率快;允许null作为Key或是value。只能在单线程使用
- 默认值:默认初始容量为16,默认加载因子为0.75(当占用率为75%时,自动扩容)。
package com.xu.Map;
/*HashMap使用:
* 存储结构:哈希表(数组+链表+红黑树)
* 利用Key的hashCode()方法和equals()方法来判断是否重复
* */
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@SuppressWarnings("all")
public class Hashmap {
public static void main(String[] args) {
//创建集合
HashMap<Ultman,String> hashMap=new HashMap<>();
//刚创建hashMap之后没有添加元素时 table=null; size=0目的节省空间
//1、添加元素
Ultman ultman = new Ultman(1,"奥特曼");
Ultman ultman1 = new Ultman(2,"赛文奥特曼");
Ultman ultman2 = new Ultman(3,"杰克奥特曼");
Ultman ultman3 = new Ultman(4,"艾斯奥特曼");
Ultman ultman4 = new Ultman(5,"泰罗奥特曼");
hashMap.put(ultman,"ultman");
hashMap.put(ultman1,"ultman");
hashMap.put(ultman2,"ultman");
hashMap.put(ultman3,"ultman");
hashMap.put(ultman4,"ultman");
hashMap.put(new Ultman(5,"泰罗奥特曼"),"ultman");//去重 :重写hashCode和equals方法
System.out.println("添加元素----------------------------");
System.out.println("元素个数:"+hashMap.size());
System.out.println(hashMap.toString());
//2、删除元素
hashMap.remove(ultman);
System.out.println("删除元素----------------------------");
System.out.println("元素个数:"+hashMap.size());
System.out.println(hashMap.toString());
//3、遍历元素:使用KeySet方法
System.out.println("遍历元素:使用KeySet方法----------------------------");
Set<Ultman> ultmen = hashMap.keySet();
for (Ultman ultman5 : ultmen) {
System.out.println(ultman5+":"+hashMap.get(ultman5));
}
//4、遍历元素:使用entry方法
System.out.println("遍历元素:使用entry方法----------------------------");
Set<Map.Entry<Ultman, String>> entries = hashMap.entrySet();
for (Map.Entry<Ultman, String> entry : entries) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
//5、判断
System.out.println("判断----------------------------");
System.out.println(hashMap.containsKey(ultman));
System.out.println(hashMap.containsValue("ultman"));
System.out.println(hashMap.isEmpty());
}
}
HashMap源码分析
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16初始容量
static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量大小 2^30
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子
static final int TREEIFY_THRESHOLD = 8;//JDK1.8后加进来的。当链表长度大于8时。
static final int UNTREEIFY_THRESHOLD = 6;//链表长度小于6时,调整成链表。
static final int MIN_TREEIFY_CAPACITY = 64;//当链表长度大于8时,并且数组长度 大于64时,调整成红黑树。
transient Node<K,V>[] table;//哈希表中的数组
transient int size;//元素个数
/*当链表长度大于8并且数组长度大于64时,才会转换为红黑树。
如果链表长度大于8,但是数组长度小于64时,还是会进行扩容操作,不会转换为红黑树。
因为数组的长度较小,应该尽量避开红黑树。因为红黑树需要进行左旋,右旋,变色操作来保持平衡。
当数组长度小于64,使用数组加链表比使用红黑树查询速度要更快、效率要更高
1、hashMap并不是在链表元素个数大于8就一定会转换为红黑树,而是先考虑扩容,扩容达到默认限制后才转换
2、hashMap的红黑树不一定小于6的时候才会转换为链表,而是只有在resize的时候才会根据 UNTREEIFY_THRESHOLD 进行转换。
*/
//无参构造:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//刚创建hashMap之后没有添加元素时 table=null; size=0。
//put方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);//hash方法用来计算位置,产生hash值。
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
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);
return null;
}
HashMap的扩容 Resize
扩容的话,这里有一个值叫做loadFactor(阈值),默认值为0.75;
当数组的 元素数量>数组大小(默认16)* loadFactor(默认0.75)
就会触发扩容,扩容是二倍扩容的 (默认是16扩容后就是32)
这时原来每个元素的下标也会改变的(因为数组的长度变了)
然后就要把每个元素重新分配下标,重新加入链表或者红黑树
HashMap的线程安全
HashMap线程不安全
在put的时候,Resize(扩容)会造成数据的覆盖
JDK1.7 因为是头插法,可能会造成循环链表
JDK1.8 是尾插法
使用HashMap怎么才能让他线程安全
使用ConcurrentHashMap,
JDK1.7的是分段数组,有Segment锁(继承于ReentrantLock)加速一小段保证并发
JDK1.8 是和HashMap一样了,数组+链表(或者红黑树)
Synchronized(锁)and CAS(compare and swap)(JVM在1.6对Synchronize的优化很好)
CAS通俗易懂,比较并替换
(CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做)
(无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的 一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的)
使用HashTable(基本是废弃的)
HashTable就是把HashMap套上了一个Synchronized
Collections.synchronizedMap()包装
使用synchronized 加上,但是这个是对某个Hash桶(数组的某个值)加锁,并不是整个map加锁,在锁定的时候别的线程也可以进行访问
Hashtable和Propertise
Hashtable:
- JDK1.0版本,线程安全,运行效率慢;不允许null作为key或者value。
Propertise:
- Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。
HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),迭代器以及速度。
- HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
- HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
- 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
- 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
- HashMap不能保证随着时间的推移Map中的元素次序是不变的。
TreeMap的使用
TreeMap:
- 实现了SortedMap接口(是Map的子接口),可以对可以自动排序
和TreeSet的使用方法一样。
Collection的工具类
- 二分查找:元素必须有序
package com.xu.Map;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/*Collection工具类*/
public class Demo2 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("7");
list.add("4");
list.add("5");
list.add("6");
list.add("8");
//sort排序
System.out.println(list);
Collections.sort(list);
System.out.println(list);
//二分查找
int i = Collections.binarySearch(list, "3");
System.out.println(i);
//copy复制
List list1 = new ArrayList();
for (int j = 0; j < list.size(); j++) {
list1.add(0);
}
Collections.copy(list1, list);
System.out.println(list1);
//反转
Collections.reverse(list);
System.out.println(list);
//打乱
Collections.shuffle(list);
System.out.println(list);
//list转成数组
Object[] objects = list.toArray(new String[10]);
System.out.println(objects.length);
System.out.println(Arrays.toString(objects));
//数组转成集合
String[] strings={"zhan","sjdiauhj0","fdhasuhfui"};
System.out.println(Arrays.toString(strings));
//受限集合,不能再添加和删除
List<String> list2 = Arrays.asList(strings);
System.out.println(list2);//数组不能是基本类型的,必须是引用类型的
}
}
HashSet和HashMap的区别
HashMap | HashSet |
---|---|
HashMap实现了Map接口 | HashSet实现了Set接口 |
HashMap储存键值对 | HashSet仅仅存储对象 |
使用put()方法将元素放入map中 | 使用add()方法将元素放入set中 |
HashMap中使用键对象来计算hashcode值 | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false |
HashMap比较快,因为是使用唯一的键来获取对象 | HashSet较HashMap来说比较慢 |
面试题
“你用过HashMap吗?” “什么是HashMap?你为什么用到它?”
HashMap可以接受null键值和值,而Hashtable则不能;
HashMap是非synchronized;
HashMap很快以及HashMap储存的是键值对等等。
“你知道HashMap的工作原理吗?” “你知道HashMap的get()方法的工作原理吗?”
“HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给get()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Entry。
关于HashMap中的碰撞探测(collision detection)以及碰撞的解决方法:
“当两个对象的hashcode相同会发生什么?“因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。链地址法 (拉链法)将所有哈希地址相同的元素都链接到同一个链表中。”
“如果两个键的hashcode相同,你如何获取值对象?”
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。面试官提醒他如果有两个值对象储存在同一个bucket,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
“你了解重新调整HashMap大小存在什么问题吗?”
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。
这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢?:)
如果某个桶中的记录过大的话(当前是TREEIFY_THRESHOLD = 8),HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n)。