数组
1.1 什么是数组
- 数组是相同类型数据的有序集合
- 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。
- 其中,每一个数据称为一个数组元素,每个数组元素都可以通过下标访问它们。
数组的四个基本特点
- 数组的长度是确定的,一经创建,大小就不能再改变
- 数组中的元素必须是同一类型数据
- 数组中的元素可以是任意类型,基本数据类型或者引用数据类型
- 数组变量属于引用类型,
数组变量是在栈内存中的,存放堆内存中对应的数组的地址
。 数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量,数组本身就是对象,Java中对象是在堆中的,因此数组无论存放的是基本类型数据还是引用类型数据。数组对象本身是在堆内存中的。
数组边界
- 下标的合法区间[0 ~ length-1]超出会报数组索引越界异常。(
ArrayIndexOutOfBoundsException
)
1.2 数组声明和创建
- 首先必须声明数组变量,才能在程序中使用数组,语法:
int[] arr; //建议使用
int arr[];
- 可以使用new来创建数组
public class Demo6 {
public static void main(String[] args) {
int[] array; //声明数组
array = new int[5]; //创建数组
array[0] = 0;//为数组中的空间赋值,不赋值就是类型的默认值
array[1] = 1;
array[2] = 2;
array[3] = 3;
array[4] = 4;
System.out.println(array.length);//获取数组长度
//普通for循环
for (int i = 0; i < array.length; i++) {
System.out.println(i);
}
//增强for循环
for(int x : array){
System.out.println(x);
}
}
}
- 数组的元素是通过索引来访问的,数组索引从0开始
- 获取数组的长度,使用length属性 arr.length
1.3 Java内存分析
堆
- 存放new的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
栈
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
方法区
- 可以被所有的线程共享
- 包含了所有的class和static变量
1.4 初始化数组
静态初始化
int[] arr ={1,2,3,4};
动态初始化
int[] arr =new int[4];
arr[0]=1;
arr[1]=2;
arr[2]=3;
arr[3]=4;
默认初始化
- 数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。即int类型默认是0,引用数据类型默认是null。
1.5 数组使用
- for each循环
- 数组作方法参数
- 数组作返回值
public class Demo {
public static void main(String[] args) {
int[] array = {1,2,3,4,4};
//求数组中的所有数总和
int sum = 0;
for (int i = 0; i < array.length; i++) {
sum +=array[i];
}
System.out.println("sum="+sum);
//求数组中最大的数
int max = array[0];
for (int i = 1; i < array.length; i++) {
if(max<array[i]){
max=array[i];
}
}
System.out.println("max="+max);
}
}
1.6 多维数组
二维数组
数组里面存放的不是数据,而是另一个数组,数组名是array[i]
里面的数组中存放的是数据,用array[i][j]来获取数据。
public class Demo1 {
public static void main(String[] args) {
//创建一个二维数组
int[][] arrays = {{1,2},{2,3},{3,4},{4,5}};
//相当于四行二列的表格
/*[4][2]
1 2 array[0]
2 3 array[1]
3 4 array[2]
4 5 array[3]
*/
//遍历这个二维数组的所有元素
for (int i = 0; i < arrays.length; i++) {
for (int j = 0; j < arrays[i].length; j++) {
System.out.println(arrays[i][j]);
}
}
}
}
1.7 数组工具类Arrays
java.util.Arrays
- 由于数组对象本身并没有什么方法可供调用,但API中提供了一个工具类Arrays可以使用,可以对数据对象进行一些基本的操作
- Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接通过类名来调用,而不用使用对象来调用(注意是‘不用’而不是‘不能’)
- 有以下一些常用功能
- 给数组赋值,通过fill方法
- 对数组排序,使用sort方法,按照升序来排序
- 比较数组,通过equals方法比较数组中的元素是否相等
- 查找数组元素,通过binarySearch方法能对排序好的数组进行二分查找法操作
1.7.1 fill()方法
static void fill(boolean[] a, int fromIndex, int toIndex, boolean val)
boolean[] a:需要填充的数组
int fromIndex:开始的数组索引
int toIndex:结束的数组索引(不包含)
boolean val:需要填充的值
public class Demo2 {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
//将第2,3个元素填充为0
Arrays.fill(a,1,3,0);
System.out.println(Arrays.toString(a)); //[1, 0, 0, 4, 5]
}
}
1.7.2 binarySearch()方法
public static int binarySearch(int[] a,int fromIndex, int toIndex,int key)
参数 :
a - 要搜索的数组
fromIndex - 要搜索的第一个元素(包括)的索引
toIndex - 要搜索的最后一个元素(排他)的索引
key - 要搜索的值
public class Demo3 {
public static void main(String[] args) {
int[] a = {0,224,335,895,2,55,111};
Arrays.sort(a);
System.out.println(Arrays.toString(a)); //[0, 2, 55, 111, 224, 335, 895]
int i = Arrays.binarySearch(a,0,7,895);
System.out.println(i); //6 返回该数第一次出现所在的索引位置
}
}
冒泡排序
public class Demo4 {
public static void main(String[] args) {
int[] a = {1,6,2,4,8,7,55};
int[] sort = sort(a);
System.out.println(Arrays.toString(sort));
}
public static int[] sort(int[] a){
//外层循环一共需要a.length-1趟
//内层循环需要a.length-1-i次
for (int i = 0; i < a.length-1; i++) {
boolean flag = false;
for (int j = 0; j < a.length-1-i; j++) {
if(a[j+1]<a[j]){
int temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
flag = true;
}
}
//如果前后两个数已经比较过了,那么就不用再进入内部循环
if(flag){
break;
}
}
return a;
}
}
1.8 稀疏数组
当一个数组中大部分元素为0,或者为同一值时,可以使用稀疏数组来保存该数组
稀疏数组的处理方式
- 记录数组一共有几行几列,有多少个不同值
- 把具有不同值的元素和行列及值记录在一个小的数组中,从而缩小程序的规模
集合
2.1 什么是集合
- 对象的容器,定义了对多个对象进行操作的常用方法,可实现数组的功能。
和数组的区别
- 数组长度固定,集合长度不固定
- 数组可以存储基本类型和引用类型,集合只能存储引用类型
java.util.*
2.2 Collection接口
2.3 List接口与实现类
2.3.1 List接口
特点:
- 有序的集合,存储元素和取出元素的顺序是一样的(存储123,取出123)
- 有索引,包含了一些带索引的方法
- 允许存储重复的元素
List 接口提供了 4 种对列表元素进行定位(索引)访问方法(特有)
- void add(int index, Object o) 在列表的指定位置插入指定元素(可选操作)。
- Object get(int index) 返回列表中指定位置的元素。
- Object remove(int index) 移除列表中指定位置的元素(可选操作)。
- Object set(int index, Object o) 用指定元素替换列表中指定位置的元素(可选操作)。
- List subList(int fromIndex,int toIndex)返回下标在fromIndex和toIndex之间的集合元素
- ListIterator<?> listIterator() 返回列表中的列表迭代器(按适当的顺序)。
- Boolean contains(Object o) 判断列表中是否包含该对象,包含返回true,否则false
- Boolean isEmpty()判断该列表是否为空,为空返回true
- int indexOf(Object o) 返回该元素在列表中的位置
- toArray() 将集合转换为数组
注意:
- 使用索引的时候,一定要防止索引越界异常
- 使用迭代器的时候,迭代器内部不能再使用Collection中的方法(并发操作异常)
- 当向集合中添加基本类型数据时,存在一个自动装箱操作
- 如果想要通过new一个在列表中已经存在的对象删除该对象,需要重写equals方法,只要两个对象的所有属性都相同,就认为这两个对象是同一对象。
public class InterfaceList {
public static void main(String[] args) {
//使用实现类ArrayList创建一个集合(多态)
List<String> list = new ArrayList<>();
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛玛哈哈");
System.out.println(list);//列表输出不是一个哈希值,说明重写了toString方法
//1. void add(int index, E element) 在列表的指定位置插入指定元素(可选操作)。
list.add(0,"玛尔扎哈");//在索引位置为0的地方添加一个元素
System.out.println(list);
//2. E get(int index) 返回列表中指定位置的元素。
String s = list.get(2);
System.out.println("索引为2的元素为:" + s);
// 3.E remove(int index) 移除列表中指定位置的元素(可选操作)。返回值是移除的元素
String s1 = list.remove(0);
System.out.println("移除索引为0的元素,该元素为:" + s1);
//4. E set(int index, E element) 用指定元素替换列表中指定位置的元素(可选操作)。
String s2 = list.set(2, "撒由那拉");
System.out.println("被替换的元素为:" + s2);
/*List集合的三种遍历方式*/
//使用普通的for循环
for (int i = 0; i < list.size(); i++) {
//public E get(int index):返回集合中指定位置的元素
String s3 = list.get(i);
System.out.println(s3);
}
/*使用迭代器*/
Iterator<String> it = list.iterator();
while (it1.hasNext()){
String s4 = it.next();
System.out.println(s4);
}
/*使用简化版的迭代器,增强for循环*/
for(String str :list){
System.out.println(str);
}
/*列表迭代器,ListIterator可以向前或者向后遍历遍历过程中允许添加,删除,修改元素*/
ListIterator<String> LI = list.listIterator();
//先从前往后迭代,指针指向最后一个位置
while (LI.hasNext()){
System.out.println(LI.nextIndex()+":"+LI.next());
}
//此时指针指向最后一个位置,从后往前迭代
while (LI.hasPrevious()){
System.out.println(LI.previousIndex()+":"+LI.previous());
}
}
}
List存放基本数据类型
public class Demo2 {
public static void main(String[] args) {
List list = new ArrayList();
//集合存放基本数据类型存在一个自动装箱操作
list.add(10);
list.add(20);
list.add(30);
list.add(40);
list.add(50);
//删除元素,如果参数是int类型,表示的是下标
//list.remove(20);索引越界异常
list.remove((new Integer(20)));
System.out.println(list); //[10, 30, 40, 50]
//sublist方法,左闭右开[)
List list1 = list.subList(0, 3);
System.out.println(list1);//[10, 30, 40]
}
}
2.3.2 List实现类
ArrayList
- 必须获取一个连续空间,数组结构实现,查询快,增删慢。中间插入一个元素,之后的元素都要往后移。
- 运行效率快,线程不安全
Vector
- 数组结构实现,查询快,增删慢
- 运行效率慢,线程安全
LinkedList
- 无须获取连续空间,链表结构实现,增删快,查询慢
ArrayList源码分析
DEFAULT_CAPACITY = 10 默认容量
如果没有向集合中添加任何元素,默认容量是0,添加一个元素之后,容量是10,每次扩容0.5倍 new = old +(old>>2)
add()方法
//自动扩容核心代码
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);
}
LinkedList基本使用
public class Demo3 {
public static void main(String[] args) {
LinkedList list = new LinkedList();
//添加数据
list.add("苹果");
list.add("香蕉");
list.add("橘子");
list.add("芒果");
list.add("草莓");
//删除数据
list.remove("苹果");
System.out.println(list);//[香蕉, 橘子, 芒果, 草莓]
//遍历数据
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//判断
System.out.println(list.contains("香蕉"));//true
System.out.println(list.isEmpty());//false
//获取
System.out.println(list.indexOf("草莓"));//3
}
}
2.4 泛型和工具类
- 泛型是JDK1.5之后的新特性,本质是参数化类型,把类型作为参数传递
- 泛型类,泛型接口,泛型方法
- 提高代码的重用性,防止类型转换异常,提高代码安全性
自定义一个泛型的类
/* 创建一个泛型的类 */
public class Generic<E> {
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
根据该类创建对象
- 自定义一个含有泛型的类,模拟ArrayList集合
- 泛型是一个未知的数据类型,当我们不确定使用什么类型的数据时,可以使用泛型、
- 泛型可以接受任意类型的数据,可以使用Integer,String,Student…
- 创建对象的时候确定泛型的数据类型
public class Main {
public static void main(String[] args) {
//创建一个String类型的对象
Generic<String> sc = new Generic<>();
sc.setName("迪丽热巴");
String s = sc.getName();
System.out.println("s:" + s);
//创建一个Integer类型的对象
Generic<Integer> sc1 = new Generic<>();
sc1.setName(2);
Integer n = sc1.getName();
System.out.println("n:" + n);
}
}
定义一个泛型接口
/* 定义一个泛型的接口 */
public interface GenericInterface <I>{
public abstract void show(I i);
}
含有泛型接口的两种使用方式
/*
* 含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型
*
* 用Scanner类举例
* public interface Iterator<E>{
* E next();
* }
* Scanner类实现了Iterator接口,并指定接口的泛型为String,重写的next方法泛型默认是String
* public final class Scanner implements Iterator<String>{
* public String next(){...}
* }
* */
public class GenericInterfaceImpl1 implements GenericInterface<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
==============================================================================
/*
含有泛型的接口第二种使用方式,接口使用什么泛型,实现类就使用什么泛型,类跟着接口走
就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型
用ArrayList来举例,ArrayList类实现了List的接口
public interface List<E>{
boolean add(E e);
E get(int index);
}
public class ArrayList<E> implements List<E>{
public boolean add(E e){...}
public E get(int index){...}
}
*/
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
@Override
public void show(I i) {
System.out.println(i);
}
==============================================================================
public class Main {
public static void main(String[] args) {
GenericInterface i = new GenericInterfaceImpl1();
i.show("Method");
GenericInterface<Integer> i2 = new GenericInterfaceImpl2();
i2.show(22);
GenericInterface<String> i3 = new GenericInterfaceImpl2();
i3.show("SSS");
}
}
自定义一个泛型方法
定义含有泛型的方法:泛型定义在修饰符和返回值类型之间
格式:
修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
方法体;
}
- 含有泛型的方法,在调用方法的时候确定泛型的数据类型
- 传递什么类型的参数,泛型就是什么类型
public class MethodGeneric {
//泛型的代表字母可以任意
public static <M> void show(M m){
System.out.println(m);
}
}
==================================================
public class Main {
public static void main(String[] args) {
//使用泛型方法
MethodGeneric.show("张三");
MethodGeneric.show(2);
MethodGeneric.show(true);
}
}
泛型的通配符
?:代表任意的数据类型
- 不能创建对象使用,只能作为方法的参数使用
public class WildCard {
public static void main(String[] args) {
ArrayList<Integer> i1 = new ArrayList<>();
i1.add(1);
i1.add(2);
printArray(i1);
ArrayList<String> i2 = new ArrayList<>();
i2.add("a");
i2.add("b");
printArray(i2);
}
//创建一个方法,无论参数是什么数据类型,都能将集合中的元素遍历输出
//注意:
//泛型没有继承概念的,因此参数ArrayList<?>不能改为ArrayList<Object>
public static void printArray(ArrayList<?> list){
Iterator<?> it = list.iterator();
while(it.hasNext()){
//it.next()方法,取出的元素是Object,可以接受任意的数据类型
System.out.println(it.next());
}
}
}
泛型集合
- 参数化类型、类型安全的集合,强制集合元素的类型必须一致
特点
- 编译时即时检查,而非运行时抛出异常
- 访问时,不必类型转换
- 不同泛型之间引用不能相互赋值,泛型不存在多态
public class Demo1 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//list.add("1");编译时就会报错
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());//不用再类型转换
}
}
}
2.5 Set接口和实现类
2.5.1 Set接口
- 特点:无序,无下标,元素不可重复
- 方法:全部继承Collection接口的方法
2.5.2 Set实现类
HashSet
- 基于HashCode实现元素不重复
- 当存入元素的哈希码相同时,会调用equals确认,如结果为true,拒绝后者存入
- 存储结构:(数组+链表)JDK1.8之后:(数组+链表+红黑树)
TreeSet
- 基于排列顺序实现元素不重复
- 实现了SortedSet接口,对集合元素自动排序
- 元素对象的类型必须实现Comparable接口,指定排序规则
- 通过CompareTo方法确定是否为重复元素,该方法返回值是0,表示是重复元素
- 存储结构:红黑树
HashSet基本使用
public class Demo1 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
//1.添加
set.add("小米");
set.add("苹果");
set.add("华为");
set.add("苹果"); //重复数据添加不进去
System.out.println(set); //[苹果, 华为, 小米]
System.out.println("总元素个数"+set.size()); //3
//2.删除
set.remove("小米");
System.out.println(set); //[苹果, 华为]
//3.遍历
//使用增强for循环
for (String s : set) {
System.out.println(s);
}
//使用迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//4.判断
System.out.println(set.contains("小米")); //false
System.out.println(set.isEmpty()); //false
//5.set集合没有下标,无法获取索引
}
}
HashCode存储过程(重复依据)
- 根据hashcode计算保存的位置,如果此位置为空,则直接保存,如果不为空执行下一步
- 执行equals方法,如果equals方法为true,认为重复数据,否则,形成链表
如果不重写hashcode和equals方法
set.add(new Student(“小新”,5));可以被成功添加,因为hashcode不同。
public class Demo2 {
public static void main(String[] args) {
Student s1 = new Student("小新",5);
Student s2 = new Student("风间",4);
Student s3 = new Student("阿呆",6);
HashSet<Student> set = new HashSet<>();
set.add(s1);
set.add(s2);
set.add(s3);
set.add(new Student("小新",5));
System.out.println(set.size());//4
}
}
重写hashcode和equals之后,姓名和年龄都相同就被认为是同一个对象,不能被重复添加到set集合中。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
查看hash方法的源码
为什么要用31
- 31是一个质数,减少散列冲突
- 提高执行效率 31* result = (result<<5)-result (在计算机中,位运算是效率最高的),左移5位表示result*25
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
treeSet
存入的数据必须要实现Comparable<?>接口,并且规定排序规则(实现compareTo方法)
java.lang.ClassCastException: 类型转换异常
重写compareTo方法,定义排序规则
@Override
public int compareTo(Student s) {
int a = this.getName().compareTo(s.getName());
int b = this.age - s.age;
//先比较姓名,如果姓名相同,比较年龄,按照年龄升序排列
return a==0 ? b:a ;
}
实现Comparable接口,并且重写compareTo方法之后
public class Demo1 {
public static void main(String[] args) {
Student s1 = new Student("a小新",5);
Student s4 = new Student("a小新",4);
Student s2 = new Student("a风间",4);
Student s3 = new Student("c阿呆",6);
TreeSet<Student> treeSet = new TreeSet<>();
treeSet.add(s1);
treeSet.add(s2);
treeSet.add(s3);
treeSet.add(s4);
for (Student student : treeSet) {
System.out.println(student);
}
}
}
/*
Student{name='a小新', age=4}
Student{name='a小新', age=5}
Student{name='a风间', age=4}
Student{name='c阿呆', age=6}
*/
存储对象的类不实现Comparable接口
- 在创建的时候就指定比较规则,使用匿名内部类
public class Demo2 {
public static void main(String[] args) {
Student s1 = new Student("a小新",5);
Student s4 = new Student("a小新",4);
Student s2 = new Student("a风间",4);
Student s3 = new Student("c阿呆",6);
TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//先按年龄,再按姓名
int a = o1.getAge()-o2.getAge();
int b = o1.getName().compareTo(o2.getName());
return a==0?b:a ;
}
});
treeSet.add(s1);
treeSet.add(s2);
treeSet.add(s3);
treeSet.add(s4);
System.out.println(treeSet);
}
}
/*
Student{name='a小新', age=4}
Student{name='a风间', age=4}
Student{name='a小新', age=5}
Student{name='c阿呆', age=6}
*/
使用lambda表达式简化
TreeSet<Student> treeSet = new TreeSet<Student>(( o1, o2) ->
o1.getAge()-o2.getAge()==0?
o1.getName().compareTo(o2.getName()):
o1.getAge()-o2.getAge()
);
treeSet练习
使用treeSet按照字符串长度进行排序存储,如果长度相同,按照字符串顺序排序
public class Demo3 {
public static void main(String[] args) {
//使用treeSet按照字符串长度进行排序存储,如果长度相同,按照字符串顺序排序
TreeSet<String> treeSet = new TreeSet<>(
(o1,o2)->o1.length()-o2.length()==0?
o1.compareTo(o2):
o1.length()-o2.length());
treeSet.add("asdf");
treeSet.add("asdfeaf");
treeSet.add("asdfa");
treeSet.add("asdd");
treeSet.add("asdfdsaf");
for (String s : treeSet) {
System.out.println(s);
}
}
}
2.6 Map接口和实现类
2.6.1 Map接口
- 用于存储键值对(K,V),键不能重复,值可以重复,无序集合
方法
- V put(K key,V value),将对象存储到集合中,key重复则覆盖原值
- Object get(K key),根据键获取对应的值
- Set< K > keySet(),返回所有的key,返回值是一个set集合,不能重复
- Collection< V > values(),返回所有的值,返回值是一个集合,可以重复
- Set<Map.Entry<K,V>> entrySet() ,返回键值匹配的set集合 ,不能重复
public class Demo1 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"小新");
map.put(5,"小葵");
map.put(8,"风间");
map.put(4,"阿呆");
map.put(2,"正南");
//遍历map集合,获取key。根据key获取value
Set<Integer> keySet = map.keySet();
for (Integer key : keySet) {
System.out.println(key + "::"+map.get(key));
}
//获取所有的value
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
}
//根据键获取对应的值
String s = map.get(8);
System.out.println(s);
//获取所有的键值对集合
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key+":"+value);
}
//判断集合中是否有key或者value
System.out.println(map.containsKey(6)); //false
System.out.println(map.containsValue("小白")); //false
}
}
对于遍历map集合的两种方法,entryset方法效率高于keyset方法
2.6.2 Map实现类
hashMap
- JDK1.2,线程不安全,运行效率快;允许用null作为key或者是value。
- 默认容量16.ArrayList默认容量是10.
- 默认加载因子0.75。当存放的数据超过总容量的0.75时,开始自动扩容
- 存储结构:哈希表(数组+链表+红黑树)
- 使用key的hashcode和equals方法来比较两个对象是否是同一对象。
- JDK1.8新特性:当链表长度大于8,并且数组长度大于64的时候,链表结构自动转换为红黑树结构
源码
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //初始容量大小
static final int MAXIMUM_CAPACITY = 1 << 30; //数组最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认加载因子
static final int TREEIFY_THRESHOLD = 8; //链表长度大于8时,调整为红黑树
static final int UNTREEIFY_THRESHOLD = 6; //链表长度小于6,调整成链表结构
//当链表长度大于8,并且集合元素个数大于64时,调整为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table; //哈希表中的数值
transient int size; //元素个数
刚创建好HashMap之后,table=null; size=0.目的是节省空间
添加第一个元素之后,数组的容量变为16.
当数组的有效元素个数超过16*0.75=12时,数组自动扩容,长度>>1即每次扩容为前一次的2倍。
JDK1.8之前是头插入,链表中的数据替换数组中的元素,数组中的元素后移
JDK1.8之后是尾插入,数组中的元素不变,新的元素依次跟在数组元素后面,形成链表
hashMap和hashSet的关系
- HashSet的构造方法,就是创建一个HashMap
- HashSet的add方法,调用的是HashMap中的put方法
//HashSet的构造方法,就是创建一个HashMap
public HashSet() {
map = new HashMap<>();
}
===========================================
//HashSet的add方法,调用的是HashMap中的put方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
2.7 properties类
hashTable类
- JDK1.0版本,线程安全,运行效率慢,不允许null作为key或者value(基本不用)
properties类
- hashTable的子类,要求key和value都是String类型,通常用于配置文件的读取
2.8 treeMap类
- 实现了SortedMap接口(是Map的子接口),可以对key自动排序
public class Demo1 {
public static void main(String[] args) {
Student s1 = new Student("a小新",5);
Student s4 = new Student("a小新",4);
Student s2 = new Student("a风间",4);
Student s3 = new Student("c阿呆",6);
//创建一个TreeMap集合,定义排序规则
TreeMap<Student,Integer> treeMap = new TreeMap<Student,Integer>((
(o1, o2) ->
o1.getName().compareTo(o2.getName()) == 0 ?
o1.getAge()- o2.getAge():
o1.getName().compareTo(o2.getName())
));
//添加数据
treeMap.put(s1,1);
treeMap.put(s2,4);
treeMap.put(s3,2);
treeMap.put(s4,3);
//获取数据
Set<Map.Entry<Student, Integer>> entries = treeMap.entrySet();
for (Map.Entry<Student, Integer> entry : entries) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
treeSet和treeMap的关系
- treeSet的构造方法,就是创建一个treeMap
- treeSet的add方法,调用的是treeMap中的put方法
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
=====================================================
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
2.9 Collection工具类
- 集合工具类,定义了一些除了存取之外的集合其他常用方法
public class Demo1 {
public static void main(String[] args) {
//集合的工具类
ArrayList<String> list = new ArrayList<>();
list.add("1鼠");
list.add("4兔");
list.add("3虎");
list.add("5龙");
list.add("2牛");
//sort排序
Collections.sort(list);
System.out.println(list); //[1鼠, 2牛, 3虎, 4兔, 5龙]
//binarySearch二分法查找元素所在的位置
int i = Collections.binarySearch(list, "2牛");
System.out.println(i); //1 返回索引,如果找不到,返回负数
//copy复制
ArrayList<String> list2 = new ArrayList<>();
for (int i1 = 0; i1 < list.size(); i1++) {
list2.add("a");
}
Collections.copy(list2,list);
//被复制的数组长度不能比原数组长度短,否则会抛出资源不匹配异常
System.out.println(list2);
//reverse反转
Collections.reverse(list);
System.out.println(list);//[5龙, 4兔, 3虎, 2牛, 1鼠]
//shuffle打乱
Collections.shuffle(list);
System.out.println(list);//[3虎, 4兔, 1鼠, 5龙, 2牛],每次运行结果都不同
//集合转换为数组
String[] array = list.toArray(new String[5]);
System.out.println(Arrays.toString(array));
//数组转换为集合(转换后的集合是一个受限集合,不能添加和删除)
String[] names = {"a","b","c"};
List<String> strings = Arrays.asList(names);
System.out.println(strings);
}
}