文章目录
一、数组的定义及相关概念
1.1 定义
1.定义:数组是多个相同类型数据按一定顺序排列的组合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。
2.相关概念:
<1>元素
<2>下标(角标、索引)
<3>数组名
<4>数组的长度
3.数组:属于引用数据类型。数组的元素可以是基本数据类型也可以是引用数据类型。
4.数组的分类:
<1>按照维度:一维数组、二维数组、三维数组。
<2>按照元素的类型:基本数据类型变量的数组、引用数据类型变量的数组
1.2 数组的特点
1.数组一旦初始化完成,数组的长度确定。长度一旦确定,就不可更改。
2.数组中的元素是在内存空间中连续存储的。
1.3 一维数组的使用和默认初始化
不同类型的数组的默认初始化的情况:
整型:0
浮点型:0
字符型:\u0000 或0,不等同于’0’
布尔型:false
引用类型:null,不等同于"null"
此时的规范与后面类的属性的默认初始化值一致!
public class ArrayTest {
public static void main(String[] args) {
//1.数组的声明和使用
int[] arr1;//声明
//静态初始化:数组的初始化和数组元素的赋值同时进行
arr1 = new int[]{1,2,3,4};
//合并声明和初始化
int[] arr2 = new int[] {1,2,3,4,5,6};
//或:int arr2[] = new int[]{1,2,3,4,5,6};
//或:int[] arr2 ={1,2,3,4,5,6};
//动态初始化:数组的初始化和数组元素的赋值分开进行
String[] arr3 = new String[3];
//2.如何调用数组的元素:通过角标调用,从0开始,到长度n-1结束
arr3[0] = "刘二";
arr3[1] = "张三";
arr3[2] = "李四";
//数据的重新赋值
arr3 = new String[5];
//错误的声明方式
//int[] arr1 = new int[4]{1,2,3,4};
//3.数组的长度:使用length属性调用
System.out.println(arr3.length);//5
System.out.println(arr2.length);//6
int[] arr4 = new int[3];
//4.遍历数组元素
for(int i = 0;i<arr4.length;i++) {
System.out.println(arr4[i]);
}
}
}
1.4 二维数组的使用
1.如果数组的元素又是数组类型,则构成了多维数组。
2.二维数组的默认初始化
二维数组的外层元素(例如:arr[1] [ ] )的初始化值为:地址值或null
二维数组的内层元素(例如:arr[1] [1] )的初始化值为:
如果外层元素初始化值为地址值时,此时内层元素默认初始化值于一维相同;
如果外层元素初始化值为null时,则内层元素还不存在,调用时报错。
补充: 查看二维数组变量的值,只要初始化过二维数组,存储都是地址值。
数组的常用算法:复制、反转、查找
public class ArrayTest1 {
public static void main (String[] args) {
//1.二维数组的声明和初始化
int[] arr1 = new int[] {1,2,3};//一维初始化
//静态初始化
int[][] arr2 = new int[][] {{1,2,3},{4,5},{7,8}};
//动态初始化方法1
int[][] arr3 = new int[3][2];
//动态初始化方法2
int[][] arr4 = new int[3][];//错误方法:int[][] arr4 = new int[][2];
arr4[0] = new int[3];
arr4[1] = new int[4];
arr4[2] = new int[2];
//2.使用数组元素:使用角标方式
System.out.println(arr2[1][0]);
arr4[1][3] = 1; //赋值
//3.数组的长度
System.out.println(arr2.length);//3
System.out.println(arr2[1].length);//2
//4.二维数组的遍历
System.out.println("********");
for(int i = 0;i<arr2.length;i++) {
for(int j = 0;j < arr2[i].length;j++) {
System.out.print(arr2[i][j]);
}System.out.println();
}
二维数组的默认初始化值
//5.二维数组的默认初始化值
//外层元素:
System.out.println(arr3[0]); //[I@15db9742 地址值
System.out.println(arr3[1]); //[I@6d06d69c
System.out.println(arr3[2]);//[I@7852e922
//内层元素:
System.out.println(arr3[0][0]); //0
System.out.println("********");
String[][] arr5 = new String[3][2];
System.out.println(arr5[0]); //[Ljava.lang.String;@4e25154f
System.out.println(arr5[1]); //[Ljava.lang.String;@70dea4e
System.out.println(arr5[2]);//[Ljava.lang.String;@5c647e05
System.out.println(arr3);//[[I@33909752
System.out.println(arr5);//[[Ljava.lang.String;@55f96302
System.out.println("********");
int[][] arr11 = new int[3][];
double[][] arr12 = new double[3][];
String[][] arr13 = new String[3][];
System.out.println(arr11);//[[I@3d4eac69
System.out.println(arr11[0]); //NUll
System.out.println(arr11[1]); //NUll
System.out.println(arr11[2]);//NUll
// System.out.println(arr11[2][0]);//报错:Exception in thread "main" java.lang.NullPointerException
System.out.println(arr12);//[[D@42a57993
System.out.println(arr12[0]); //NUll
System.out.println(arr12[1]); //NUll
System.out.println(arr12[2]);//NUll
System.out.println(arr13);//[[Ljava.lang.String;@75b84c92
System.out.println(arr13[0]); //NUll
System.out.println(arr13[1]); //NUll
System.out.println(arr13[2]);//NUll
二位数组的内层解析和常用算法:复制、反转、查找
//6.二位数组的内层解析
//复制
String[] arrs1 = new String[] {"AA","BB","CC","DD","JJ","GG"};
String[] arrs2 = new String[arrs1.length];
for(int i = 0;i < arrs1.length;i++) {
arrs2[i] = arrs1[i];
}
//反转
// for(int start = 0,end = arrs1.length -1;
// start < end;start++,end--) {
// String temp = arrs1[start];
// arrs1[start] = arrs1[end];
// arrs1[end] = temp;
// }
//遍历
for(int i = 0;i < arrs1.length;i++) {
System.out.println(arrs1[i]);
}
//查找(搜索)
//方式一:线性查找
String str = "GG";
boolean isFlag = true;
for(int i = 0;i < arrs1.length;i++) {
if(arrs1[i].equals(str)) { //比较两个字符串的内容是否相等
System.out.println("找到了!索引位置为: " + i);
isFlag = false;
break;
}
}
if(isFlag) {
System.out.println("不好意思,没有找到!");
}
//方式二:二分法查找,前提:数组要有序
int[] arr22 = new int[] {-99,-54,-2,0,2,33,43,256,999};
boolean isFlag1 = true;
int number = 255;
int head = 0,end = arr22.length -1;
while(head <= end) {
int middle = (head + end) / 2;
if(arr22[middle] == number) {
System.out.println("找到了指定元素,索引为: " + middle);
isFlag = false;
break;
}else if(arr22[middle] > number) {
end = middle -1;
}else {head = middle + 1;}
}
if(isFlag1) System.out.println("未找到指定元素!");
//数组元素的排序算法,详见Java实现冒泡排序、快速排序算法文章
}
}
二、集合的概念
2.1 Collection和Map
JDK在数组和链式结构基础上,重新设计了很多容器类型。
两大类:一、Collection :一组对象,比喻“单身party”;二、Map:键值对,(key,value),比喻“情侣party”,“家庭party”
容器的共同的行为特征,操作方式:增、删、改、查…
把容器的操作的行为标准化,用接口来声明。
三、Colletion
Collection是层次结构中的根接口(在java.util.Colletion),Collection表示一组对象。
JDK不提供此接口的任何直接实现:它提供更具体的子接口(如Set和List)实现。
3.1 Collection的常用方法
1.添加 add、addAll
add(Object obj) 一次添加一个
addAll(Collection other) 一次添加多个,把other中的元素都添加到当前集合中。即this = this ∪(并) other
2.删除 remove、removeAll、clear
remove(Object o) 一次删除一个
removeAll(Collection other) 一个删除多个,this = this - this ∩ other
clear() 清空,删除所有的对象
3.修改 Collection根接口没有提供修改的方法!
4.查询 contain、containAll、isEmpty()
contain(Object o) 判断o是否在当前集合中
containAll(Collection c) 判断c中的元素是否都在当前集合中,即判断c是否是this的子集
boolean isEmpty()
注意: Collection根接口中没有提供获取一个元素的方法。
5.获取有效元素个数 int size()
6.遍历 Object[] toArray()、foreach、Iterator
(1)老方法 Object[] toArray() :如果底层实现就是数组比较简单,如果不是,效率比较低。无论底层如何,都会返回一个size长度的数组,浪费空间。
(2)foreach
(3)Iterator 迭代器遍历
特殊: retainAll(Collection c) 保留当前集合和c集合的交集,即this = this ∩ c
@Test
public void test1() {
//多态引用,关注Collection的方法,ArrayList是Collection的子接口List的实现类。
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
c.add("钱七");
Collection other = new ArrayList();
other.add("王五");
other.add("赵六");
c.remove("张三");
c.removeAll(other);
Object[] array = c.toArray();
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
3.2 foreach 遍历
foreach:增强版for循环
语法结构:
for(元素类型 元素名 : 可迭代的容器名){ }
可迭代的容器名:例如数组和集合
把元素名看成形参,每循环一次,把数组或集合的元素依次作为实参赋值给他。
- 即:如果元素是基本数据类型,那把数组或集合的元素“数据值”赋给它,它怎么修改和实参无关。
- 元素是引用数据类型,那把数组或集合的元素“地址值”赋给它,那么它的属性修改和实参有关,
对它的地址值修改和实参无关。 - 结论: 如果你只是查看数组和集合的元素,foreach比较简单; 如果涉及删除、修改就不要用了。
@Test
public void test1() {
int[] arr = { 1, 2, 3, 4, 5 };
// 元素类型 int,元素名自己取
for (int num : arr) {
System.out.println(num);
}
String[] names = { "张三", "李四", "王五" };
for (String str : names) {
System.out.println(str);
}
}
@Test
@SuppressWarnings("all")
public void test2() {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
c.add("钱七");
for (Object obj : c) {
System.out.println(obj);
}
}
3.3 Iterator 迭代器类型
java.util.Iterator 迭代器类型:用于迭代(遍历)Collection系列的集合。
3.3.1步骤:(1)通过集合对象拿迭代器对象;(2)Iterator方法迭代
1.先通过Collection系列集合的对象拿到迭代器对象
2.再通过Iterator的方法进行迭代
3.3.2 三个方法:hasNext、next、remove
- boolean hasNext() 判断集合是否有下一个元素需要迭代
- Object next() 取出下一个元素
- void remove() 删除刚跟迭代的元素
Iterator 与foreach方法最大的区别是Iterator 可以进行元素的删除、修改操作!
@Test
public void test1() {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
c.add("钱七");
// 1.先通过Collection系列集合的对象拿到迭代器对象
Iterator iterator = c.iterator();
// 2.再通过Iterator的方法进行迭代
while (iterator.hasNext()) {// 判断集合中是否有下一次需要迭代
Object next = iterator.next();
System.out.println(next);
}
}
@Test
public void test() {
Collection c1 = new ArrayList();
c1.add(new Student(1, "张三", 90));
c1.add(new Student(2, "李四", 43));
c1.add(new Student(3, "王五", 67));
c1.add(new Student(4, "赵六", 87));
c1.add(new Student(5, "钱七", 65));
Iterator iterator = c1.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
Student stu = (Student) next;
if (stu.getScore() < 60) {
iterator.remove();// 调用迭代器的remove,而不是集合的remove!
}
}
for (Object object : c1) {
System.out.println(object);
}
}
}
注意: 用迭代器或foreach遍历时,再用集合对象的remove方法,会产生ConcurrentModificationException(并发异常)因为迭代器和集合两条线同时操作元素。
3.4 List
3.4.1 List 概述
List: (java.util.List 接口)
即列表:可重复的,有序的(按顺序存储)
(1)有序:可以对元素的索引index,进行控制
(2)可重复
实现类:例如ArrayList、Vector(动态数组)、LinkedList:双向链表、双端队列、Stack:栈、队列 Queue、双端队列Deque
3.4.2 List 常用方法
首先,List 继承了Collection,有它的所有的方法。List还增加了跟index相关的方法。
1.添加 add、addAll
add(int index ,E element):在index位置加入一个元素
addAll(int index,Collection c):在index位置加入多个元素
2.删除 remove(int index)
3.修改 set(int index ,E element)
Collection没有提供修改方法。
4.查询 indexOf()、lastIndexOf()、get()、subList()
int indexOf(Object o):从前往后
int lastIndexOf(Object o): 从后往前
get(int index):返回index位置元素
subList(int fromIndex,int toIndex): 截取[fromIndex,toIndex)
5.遍历 toArray\foreach\Iterator和ListIterator
新方法:ListIterator
是Iterator的子接口,Iterator有的ListIterator也有,还增加了:
- A.Iterator只能从前往后遍历,ListIterator可以从任意位置开始,从前还是从后都可。
ListIterator步骤: - 第一步:先获取ListIterator的对象: 集合对象.ListIterator()
- 第二步: 通过遍历方法 hasNext() + next() 或 hasPrevious() + previous()
- B.不仅可以在遍历时删除,还增加了set和add方法
@Test
public void test1() {
List l = new ArrayList();
l.add("张三");
l.add("王五");
l.add("赵六");
l.add("钱七");
l.add("刘八");
l.add(0,"李四");
ListIterator listIterator = l.listIterator();
//从前往后
while(listIterator.hasNext()) {
Object next = listIterator.next();
System.out.println(next);
}
//从后往前
listIterator = l.listIterator(l.size());//要让迭代器指向第一个元素
while(listIterator.hasPrevious()) {
Object previous = listIterator.previous();
System.out.println(previous);
}
//从任意位置开始遍历,例如从3往前遍历
listIterator = l.listIterator(3);//要让迭代器指向第一个元素
while(listIterator.hasPrevious()) {
Object previous = listIterator.previous();
System.out.println(previous);
}
}
3.4.3 List的常见实现类
1.Vector:动态数组
内部实现:数组,初始化大小为10
2.ArrayList:动态数组
内部实现:数组,初始化大小为10
3.LinkedList:双向链表、双端队列
内部实现:链表
4.Stack:栈、队列 Queue、双端队列Deque
Stack:后进先出(LIFO) 、先进后出(FILO)
压栈:push 弹栈:pop(移除栈顶元素) peek(返回栈顶元素,但不移除)
队列 Queue:先进先出(FIFO)
offer(e)添加方法, poll()移除队列,peek()
双端队列Deque:队头和队尾都可以添加和删除元素
3.4.4 Vector与ArrayList区别
Vector:旧版,线程安全的。扩容机制为原来的2倍。支持新、旧版迭代器
ArrayList:新版,线程不安全,扩容为1.5倍。不支持老版Enumeration迭代器
3.4.5 动态数组和LinkedList的区别
LinkedList内部实现链表,对索引相关操作效率低,插入、删除,只涉及前后的元素关系,元素类型是结点类型,Node(prev,data,next);
动态数组的内部实现是数组,对索引的相关操作效率很高,插入、删除和涉及到移动元素
结论:如果后面的操作对索引更多,选择动态数组,如果添加、删除操作多,选链表
3.5 Set
3.5.1 Set 概述
Set: 即集:不可重复的,无序的(和添加顺序无关)
java.util.Set :接口,也是Collection的子接口
(1)不支持重复
(2)无序的(和添加顺序无关)
3.5.2 Set的实现类
结论: 如果既要元素不重复,又要按大小:TreeSet
如果既要元素不重复,又要保证添加顺序:LinkedHashSet
如果只是要元素不重复:HashSet
(1)HashSet:完全无序
如何保证不重复:依据元素的equals()方法
(2)TreeSet:大小顺序,和添加顺序无关
如何保证不重复:依据元素的“大小”顺序
除了implements Comparable接口的方式外还可以,TreeSet set = new TreeSet();按照元素的自然排序。
TreeSet set = new TreeSet(定制比较器对象);按照定制比较器比较,即TreeSet set = new TreeSet(new Comparator(){});
别忘记导包,import java.util.Comparator;
(3)LinkedHashSet:遍历时可保证添加顺序,存储和添加顺序无关
LinkedHashSet是HashSet的子类,但是它的元素比HashSet的元素要多维护一个添加的顺序。
LinkedHashSet效率比HashSet低,每次添加、删除要同时考虑顺序。
LinkedHashSet和HashSet一样依据equals()方法决定是否重复。
@Test
public void test1() {
Set set = new HashSet();
set.add("张三");
set.add("李四");
set.add("王五");
for (Object object : set) {
System.out.println(object);//李四 张三 王五
}
}
@Test
public void test2() {
Set set = new TreeSet();
set.add("zhangsan");//String类型实现了Comparable
set.add("lisi");
set.add("wangwu");
for (Object object : set) {
System.out.println(object);
}
}
@Test
public void test3() {
Set set = new TreeSet();
set.add(new Student1(2, "张三", 90));//如果成功,Student要实现java.lang.Comparable
set.add(new Student1(1, "李四", 66));
set.add(new Student1(3, "王五", 87));
for (Object object : set) {
System.out.println(object);
}
}
}
class Student1 implements Comparable{
private int id;
private String name;
private int score;
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", score=" + score + "]";
}
@Override
public int compareTo(Object o) {
Student1 stu =(Student1) o;
return this.id - stu.id;//按学号大小排
}
}
四、Map
java.util.Map:
和Collection的最大的不同,是它存储**“键值对,映射关系”**
4.1 Map 常用方法
1.添加 put、putAll
put(key,value) 一次添加一对映射关系
putAll(Map map) 一次添加多对映射关系,this = this ∪ map
2.删除 remove、clear()
remove(Object key): 根据key删除一对
clear() 清空
3.修改 put
通过put可以替换value,只要key相同,就可以替换
4.查询 containsKey、containsValue、get、isEmpty()
(1)containsKey(Object key) 判断某个key是否存在
(2)containsValue(Obejct value) 判断某个value是否存在
(3)get(Object key) 根据key获取value
(4)boolean isEmpty() 是否为空
5.获取映射关系,键值对数:int size()
6.遍历 (1)Set keySet();(2)Collection values();(3) Set entrySet()
(1)Set keySet();
(2)Collection values();
(3) Set entrySet() :由entry对象构成的set,因为key不重复,那么entry对象也不会重复
@Test
public void test1() {
Map map = new HashMap();
map.put("张三","李四");
map.put("赵四",null);
map.put("王五",new String[]{"钱七","王八"});
System.out.println(map.size());//3
Set keySet = map.keySet();//因为map的key不能重复,拿出所有的key就是一个set
for (Object key : keySet) {
System.out.println(key);//张三 赵四 王五
}
Collection values = map.values();
for (Object value : values) {
System.out.println(value);//李四 null [Ljava.lang.String;@28c97a5
}
Set entrySet = map.entrySet();
for (Object entry : entrySet) {
System.out.println(entry);//张三=李四 赵四=null 王五=[Ljava.lang.String;@28c97a5
}
}
4.2 Map的实现类
1. HashTable:哈希表,最古老的
最古老的,线程安全,它的key和value不能为null
2. HashMap:哈希表
新的,线程不安全,它的key和value允许为null
3. LinkedHashMap
它是HashMap的子类,它比HashMap多维护了添加的顺序。
4. TreeMap:按照key的“大小”顺序
映射关系的顺序会按照key的“大小”顺序
要求:映射关系的key必须支持排序,即实现java.lang.Comparable接口,或单独为TreeMap指定定制比较器。
5. Properties:key和value的类型一定是String
Properties是HashTable的子类,它的key和value的类型一定是String
@Test
public void test1() {
//获取系统属性
Properties properties = System.getProperties();
Set entrySet = properties.entrySet();
for (Object object : entrySet) {
System.out.println(object);
}
4.3 HashMap 源码分析
1、JDK1.7以及以前,HashMap的底层实现是数组+链表
1.数组元素的类型:
- Map.Entry接口的类型(key,value)或HashMap.Entry内部类类型,实现了Map.Entry,(key,value,next)
2.为什么要有链表:两个对象的hash值可以相同也可能不同,在相同时数组table[index]无法存放在一起
3.数组的初始化长度:默认16,手动写必须是2的N次方 - 数组会扩容,如果不扩容,链表会变得很长,它的查询、添加效率很低
- 什么情况扩容?有一个变量threshold达到临界值时,会考虑扩容,还要看当前添加(key,value)时,
- 是否table[index] == null,如果不为空就会扩容。
- DEFAULT_LOAD_FACTOR :默认加载因子:0.75;
- threshold = table.length * 0.75=16*0.75 = 12
4.index如何计算
拿到一个key的hash值之后如何计算[index] - (1)key是null,固定位置[index] = [0]
- (2)第一步,先用hashCode的值通过hash(key)函数计算一个“hash值”;
- 第二步,再根据“hash值”与table.length -1 运算。
5.如何做到key不重复 - 如果key重复,会替换掉旧的value
- key相同,先判断hash值。如果hash值一样,判断key的地址或equals是否相等,满足则替换旧的value。
6.新的(key,value)添加到table[index],是作为table[index]的头,原来的元素作为next
2、JDK1.8,HashMap的底层实现是数组+链表(或红黑树)
1.为什么要要修改为链表或红黑树的设计? 当某个链表过长时,查询效率低。
- 如果table[index]的链表的节点个数大于8,就要考虑红黑树。TREEIFY_THRESHOLD:转化阈值
2.什么时候树化? - 如果table[index]的结点数量到8个,还要判断table.length是否达到64,如果没有,先扩容。
- 演示:8 --> 9个, length从16–>32
- 9 --> 10个, length从32–>64
- 10 --> 11个, length已到64,table[index]会从Node类型转换为TreeNode类型
- MIN_TREEIFY_CAPACITY = 64,最小树化容量
3.什么时候从树–>链表 - 当你删除结点时,这棵树的结点个数小于6个,会反树化
- UNTREEIFY_THRESHOLD = 6
4.新的(key,value)添加到table[index],JDK是在链表的尾部
成语:七上八下