数组和集合
数组的特点
数组中保存的元素都是有的,可以通过索引快速访问
数组中保存的元素都是同一类型
数组的长度在定义后,无法改变
数组无法获取其中保存的元素实际数量。
集合的特点
能保存一组数据,元素可以有序或无序(存入的顺序和读取的顺序不一致)
集合中保存的元素的数据类型可以不同
集合的容量可变
可以获取集合中保存的元素实际数量
集合家族(集合框架)
Collection接口:
List接口:
ArrayList实现类:有序,可重复,使用数组实现
LinkList实现类:有序可重复,使用链表实现
Set接口:
HashSet:无序不重复,使用HashMap实现
TreeSet实现类:可定义有序或无序,重复或不重复,由compareTo方法的返回值决定
Map接口:
HashMap实现类:使用键值对保存元素,键不重复,值可以重复。使用数组+链表+黑红树实现。
Collection接口
该接口中有两个核心子接口:List和Set。
这两个接口都可以保存一组元素,List接口保存元素有序可重复。Set接口保存元素无序不重复。
Collection接口有一个父接口Iterable,它不是一个集合,而是用于遍历集合的工具接口。包含forEach()和iterator()方法。
常用方法 | 返回值 | 作用 |
add(Object obj) | boolean | 将元素添加到集合中 |
size() | int | 获取集合中的元素数量 |
isEmpty() | boolean | 判断集合是否为空 |
clear() | void | 清空集合 |
contains(Object obj) | boolean | 判断集合中是否包含指定元素 |
remove(Object obj) | boolean | 移除集合中的指定元素 |
toArray() | Object[] | 将集合转换为数组 |
stream() | Stream | 获取集合中的流对象,用于遍历集合 |
iterator() | Iterator | 得到集合的迭代器对象,用于遍历集合 |
List接口(有序可重复)
常用方法 | 返回值 | 作用 |
get(int index) | Object | 得到指定索引的元素 |
set(int index,Object obj) | Object | 使用obj替换index上的元素,返回被替换的元素 |
add(int index,Object obj) | void | 将obj添加到index上 |
remove(int index) | Object | 移除指定索引的元素,返回被移除的元素 |
indexOf(Object obj) | int | 得到obj第一次出现的索引 |
lastIndextOf(Object obj) | int | 得到obj最后一次出现的索引 |
subList(int from,int to) | List | 截取[from,to)区间中的元素,返回子集合 |
List.of(E...element) | List | 根据参数创建一个不可变集合,该集合不能对其中的元素进行修改。 |
ArrayList实现类(掌握)
采用数组实现的集合
可以通过索引访问元素,可以改变集合大小,如果要在其中插入或删除元素时,会影响后续元素
该集合查询效率高,中途添加和删除元素效率低
集合中保存的都是引用类型,如集合中保存123,保存的不是int类型的123,而是Integer类型的123.
构造方法
构造方法 | 说明 |
ArrayList() | 创建一个Object类型的空数组,再调后续添加方法时,才会初始化数字大小为10. |
ArrayList(int initialCapacity) | 创建一个指定容量的Object类型数组,如果参数为负数会抛出IllegalArgumentException异常 |
ArrayList(Collection c) | 根据指定集合创建Object类型数组 |
常用方法
主要以List接口和Collection接口中的方法为主。
LinkedList实现类
采用双向链表实现的集合
集合中保存的每个元素称为节点,除首位节点外,其他节点既保存了自己的数据,还保存了其他前后节点的地址。
如果在双向链表的结构中进行插入和删除节点的操作时,不会影响其他节点现在保存的位置。添加的节点只需记录前后节点的位置接口。
如果要查询某个节点的地址时,需要从头结点或为节点开始搜索目标节点的位置。
双向链表在中间插入和删除数据效率高,随机读取的效率低。
构造方法
LinkedList():创建一个空双向链表
常用方法
主要以List接口和Collection接口中的方法为主。由于还实现了Deque接口,所以还有一些Deque接口中的方法。如操作尾结点的方法。
常用来自于Deque接口中的方法 | 作用 |
addFirst(Object obj) | 添加obj为头结点 |
addLast(Object obj) | 添加obj为尾结点 |
getFirst()/Last() | 得到头结点,得到尾结点 |
removeFirst()removeLast() | 移除头结点,移除尾结点 |
ArrayList和LinkedList的区别
这两个类都是List接口的实现类,保存的元素有序可重复,允许保存null。
ArrayList采用数组实现,随机读取效率高,插入和删除效率低,适用于查询
LinkedList采用双向链表实现,插入和删除效率高,随机读取效率低,适用于频繁更新集合。
Set接口 无序不重复
无序结合,元素不能重复,允许保存null,没有索引
Set接口中的方法都是继承于Collection接口
哈希表是一种数据结构,也称为散列表,能更快速的访问数据。
假设原本的数据为左侧的数据,如要查询10需要遍历数组,效率不高。
通过一个特定的函数"原始值%5",得到一组新数组,让新数据重写对应元素,保存到新数组中,这个新数组称为哈希表。
这时如果要查询10,由于哈希函数是10%5得到0,所以直接查询哈希表中0对应的元素即可。
整个过程中,这个函数称为哈希函数,得到的新数组称为哈希码,对应关系为哈希映射。
这个哈希函数,有一定几率让多个原始值得到相同的哈希码,这种情况称为哈希冲突(哈希码相同,实际只不同)。
哈希码的特点
如果两个对象的hashcode不同,这两个对象一定不同
如果两个对象的hashcode相同,这两个对象不一定相同。
hashcode相同,对象不同:哈希冲突
"通话"和"重地"两个字符串的hashcode相同,但是两个不同的对象
HashSet实现类
采用哈希表实现
元素不能重复,无序保存,允许保存一个null
本质是HashMap对象
使用HashSet集合时,通常要重写实体类中的equals和hashcode方法
构造方法
HashSet() 创建一个空集合,实际是创建一个HashMap对象
常用方法
HashSet中没有定义属于自定的方法,都是父接口Set和Collection中的方法
HashSet添加数据的原理
如果添加的两个元素的equals方法结果为true且hashcode相同时,视为同一个对象,不能添加
每次向集合添加元素时,先判断该元素的hashcode是否存在
如果不存在,视为不同对象,直接添加
如果存在,在判断equals方法的结果
如果为true,视为同一个对象,不能添加
如果为false,视为不同对象,可以添加
不能添加的条件是hashcode相同且equals的结果为true
可以只判断equals结果,但效率不高
如果只判断hashcode是否相同,效率高,但可能出现哈希冲突
equals和hashcode的关系
如果两个对象的equals方法结果为true,在没有重写equals方法的前提下,hashcode相同吗?
如果没有重写equals方法,默认使用Object中的方法,使用==判断,如果结果为true,说明是同一个地址,同一个对象,hashcode一定相同
如果两个对象的hashcode不同,在没有重写equals方法的前提下,equals的结果为:
hashcode不同,说明不是同一个对象,没有重写equals说明使用Object方法,==判断,结果为false
如果两个对象的hashcode相同,equals方法的比较结果:可能为true,可能为false
String str1="hello";
String str2="hello";
//str1和str2使用同一个地址,hashcode相同,equals结果为true
String str3="通话";
String str4="重地";
//str3和str4不是同一个地址,但hashcode相同,是哈希冲突,结果为false
HashSet的应用
如果要保存的对象保证不重复,且无关顺序,可以使用HashSet,重写要保存的元素的hashcode方法。
Student类,保证添加对象时,不重复
public class StudentManager{
private HashSet<Student>hs=new HashSet<>();
//添加时,如果对象的属性都一致,视为同一个对象,不能重复添加
public void addStudent(Student student){
hs.add(student);
}
}
TreeSet实现类
特殊的Set实现类,数据可以有序保存,可以重复,不能添加null
曹勇红黑树(自平衡二叉树)实现的集合
1.二叉树表示某个节点最多两个子节点
2.有个节点右侧值大于左侧值节点
3.红黑树会经过"变色"和旋转大道二叉树的平衡
只能添加同一种类型的对象且该对象实现了Comparable接口
1.每次调用add方法添加元素时,会自动调用Comparable接口中的方法compareTo()方法
2.实现Comparable接口后必须要重写compareTo()方法,用于决定新添加的元素放在旧元素之前或之后。
compareTo()方法的返回值决定了能否添加新元素和新元素的位置。
1.如果返回0,视为每次添加都是同一个元素,不能重复添加。
2.如果返回正数,将新元素添加到现有元素之后
3.如果返回负数,将新元素添加到现有元素之前。
添加的元素可以自动排序
构造方法
TreeSet() 创建一个空集合
常用方法
能使用Set和Collection接口中的方法,还定义了一些属于它的方法
独有方法 | 作用 |
first()last() | 得到集合中的第一个元素,得到集合中的最后一个元素 |
ceiling(Objcet obj) | 得到集合中比参数大的元素中的最小元素 |
floor(Object obj) | 得到集合中比参数小的元素中的最大元素 |
TreeSet的应用
如果要保存的元素需要对其根据某个属性排序,使用该集合
如果在集合中保存整数,即Integer对象,Integer类已经实现了Comparable接口
如果要保存自定义的元素,必须要实现Comparable接口,重写compareTo方法,自定义排序规则
Main类
Map接口
Map接口称为映射,该集合中保存的数据是以键值对的形式保存,保存的键与值的映射关系。
键称为Key 值称为Value,键不能重复,允许出现一个null作为键,值没有限制。
键和值都必须是引用类型。
yyds---yyds "yyds"是键key 永远单身是值value
常用方法 | 作用 |
size() | 得到键值对的数量 |
isEmpty() | 判断是否为空集合 |
clear() | 清空所有键值对 |
put(Object key,Object value) | 添加一组键值对 |
get(Object key) | 根据键值对得到对应的值 |
containsKey(Object key) | 判断是否存在某个值 |
containsValue(Object value) | 判断是否存在某个值 |
keyset() | 得到键的集合 |
values() | 得到值的集合 |
entrySet() | 得到键值对的集合 |
remove(Object key) | 删除指定的键值对 |
HashMap实现类
构造方法
HashMap() 创建一个大小为16,加载因子为0.75的空集合
常用方法
使用Map接口中的方法
HashMap采用"数组+链表+红黑树"实现
当没有出现哈希冲突时,元素保存在数组中
如果出现哈希冲突,在对应的位置上创建链表,元素保存到链表中
如果链表的长度大于8,将链表转换为红黑树
遍历集合的方式
遍历List集合
普通for循环
for(int i=0;i<集合.size();i++){
元素 变量=集合.get(i)
}
增强for循环
for(数据类型 变量名:集合){
元素 变量=集合.get(i)
}
forEach()方法
使用该方法遍历集合时,不要使用add或remove操作,遍历会抛出异常
集合.forEach(obj->{
元素 变量=集合.get(i);
});
迭代器
//collection接口有一个父接口Iterable,其中有一个iterator方法用获取迭代器对象遍历集合
//所有Collection的子实现类都能调用该方法
Iterator it=Collection集合.iterator();
//hasNext()判断是否还有下一个元素
while(it.hasNext()){
//next()方法读取钙元素
元素 变量=it.next();
}
遍历Set集合
普通for循环无法遍历Set集合,因为元素没有索引
增强for循环
for(数据类型 变量名:集合){
元素 变量=集合.get(i);
}
forEach()方法
集合.forEach(obj->{
元素 变量=集合.get(i);
});
迭代器
//Collection接口有一个父接口Iterable,其中有一个iterator方法用于获取迭代器对象遍历集合
//所有Collection的子实现类都能调用该方法
Iterator it = Collection集合.iterator();
//hasNext()判断是否还有下一个元素
while(it.hasNext()){
//next()方法读取该元素
元素 变量 = it.next();
}
遍历HashMap集合
Set keySet=集合.keySets();
for(object key :keySet){
Object value=集合.get(key);
}
泛型
一种规范,常用于限制集合中的元素类型。省去遍历集合时转换Object对象的过程
//默认可以保存任意类型的元素,即Object类型
List list=new ArrayList();
list.add(123);
list.add("hello");
//遍历时只能使用Object类型获取
for(Object obj:list){
}
使用泛型
在定义集合时,在集合类或接口后协商<引用数据类型>
集合类或接口<引用数据类型>集合遍历名=new 集合实现类();
//定义只能泡村整数的集合,要是有整数的包装类型
List<Integer>list=new ArrayList();
list.add(123);
//不能添加非整数
//list.add("hello");
Collections集合工具类
Collection是集合的根接口,定义了操作集合中元素的方法
Collections是集合的工具类,定义了操作集合中元素的静态方法。
Collections中的静态方法 | 说明 |
Collections.shuffle(List list) | 打乱集合中元素的顺序 |
Collections.sort(List list) | 对集合中的元素进行排序,元素必须实现Comparable接口 |
Collections.swap(List list,int a,int b) | 将集合中索引a和b的元素交换位置 |
Collections.reverse(List list) | 反转集合中的元素 |
Collections.max(Collection c) | 得到集合中元素的最大值,元素必须实现Comparable接口 |
Collections.rotate(List list,int distance) | 将集合中最后distance个元素放在集合最前 |
Collections.fill(List list,Object obj) | 使用obj填充集合 |
Arrays数组工具类
包含一些操作数组的静态方法
常用静态方法 | 说明 |
Arrays.sort() | 对数组中的元素升序排列 |
Arrays.asList(T...obj) | 将可变参数转换为ArrayList集合 |
集合与数组之间的转化
数组转换为集合
//调用Arrays工具类的asList(1,2,3,4,5)或asList(数组)
ArrayList<Integer>list=Arrays.asList(1,2,3,4,5);
集合转换为数组
ArrayList list=new ArrayList();
list.add("sdf");
list.add(123);
list.add(null);
//调用集合的toArray()方法
Object[] objs=list.toArray();
无论数组转集合还是集合转数组,都可以进行遍历
如果集合转换为数组,遍历集合,通过索引赋值
如果数组转换为集合,遍历数组,通过add()添加元素
文件类File
Java中的File类,表示本地硬盘中的文件file或文件夹directory的一个类
通过这个类创建对象,可以读取文件信息或操作对应文件。
构造方法
常用构造方法 | 说明 |
File(String pathName) | 根据文件的完整路径创建File对象 |
File(String parent,String child) | 根据文件的父目录的路径和自身的名称创建File对象 |
File(File parent,String child) | 根据文件的父目录对应的File对象和自身的名称创建File对象 |
File file1 = new File("C:\\Users\\Administrator\\Desktop\\230202.txt");
File file2 = new File("C:\\Users\\Administrator\\Desktop", "230202.txt");
File parent = new File("C:\\Users\\Administrator\\Desktop");
常用方法
常用方法 | 作用 |
exists() | 判断文件是否存在 |
isFile() | 判断是否为文件 |
isDirectory() | 判断是否为文件夹 |
getName() | 获取文件名 |
getPath() | 获取文件相对路径 |
getAbsolutePath() | 获取文件绝对路径 |
length() | 获取文件字节大小 |
delete() | 删除文件或空文件夹 |
mkdir() | 创建文件夹 |
listFiles() | 得到文件夹中的第一层子文件对象的数据,返回File[] |
//递归调用
public static int fun(int n){
if(n>2){
return fun(n-1)+fun(n-2);
}
return 1;
}
递归遍历文件夹
IO
I:input输入
O:output输出
流Stream
流表示计算机硬盘与内存之间传输数据的通道
将内存中的数据存入到硬盘中,称为写write,也称为输出Output
将硬盘中的数据存入到内存中,也成为了读read,也称为输入Input
流的分类
字节输入流InputStream
FileInputStream ObjectInputStream
字节输出流OutputStream
FileOutputStream ObjectOutputStream
字符输入流Reader
FileReader、BufferedReader、InputStreamReader
字符输出流Writer
FileWriter、BufferedWriter、OutputStreamWriter
按方向分类
输入流:InputStream Reader
读取硬盘中的数据到程序中
输出流:OutputStream Writer
将程序中的数据写到硬盘中
按数据传输类型分类:
字节流:InputStream OutputStream
读写非文本类型文件,如图片,多媒体文件
字符流:Reader,Writer
读写纯文本文件,如txt,md等
如要将硬盘中的某个txt文件中的内容读取到程序中,使用Reader
如要将硬盘中的某个图片文件中的内容读取到程序中,使用InputStream
如要将程序中的文本写入到硬盘中,保存为txt文件时,使用Writer
如果要将程序中的数据写入到硬盘中,保存为非文本文件时,使用OutputStream
流的四个父类的特点
这四个父类都是在java.io包下,都是抽象类,不能直接创建其对象,使用其子类对象
这四个类都有close()方法,用于关闭流对象,释放资源。
输入流(InputStream和Reader)都有read()方法,用于读取数据,输出流(OutputStream和Writer)都有write()方法
输出流(OutputStream和Writer)都有flush()方法,用于将流中的数据冲刷到硬盘中
在使用输出流对象时,一定要将调用flush()或close()方法后,才能真正将数据写入到硬盘中
所有流中,以Stream结尾,都是字节流,数据以字节传输,以Reader或Writer结尾,都是字符流,数据以字符传输。
读取硬盘中的数据时,读取的文件必须存在,写入数据到硬盘中时,写入的文件可以不存在,但父目录必须存在。
FileInputStream文件字节输入流
以字节的形式读取文件
构造方法
常用构造方法 | 作用 |
FileInputStream(String filePath) | 根据文件完整路径创建流对象 |
FileInputStream(File file) | 根据文件对象创建流对象 |
常用方法
常用方法 | 作用 |
read() | 读取一个字节,返回读取到的字节 |
read(byte[] bytes) | 读取指定数组大小的字节,返回读取到的字节数量 |
read(byte[]buyes,int off,int len) | 读取指定数组大小的字节,从off索引开始读取len个字节,返回读取到的字节数量 |
available() | 文件可读取的最大字节数量 |
close() | 关闭流对象 |
使用FileInputStream和FileOutputStream读写时的注意事项
在通过FileInputStream对象使用read(byte[] bytes)方法时,每次读取指定数组的字节,将读取到的字节保存在字节数组中。
该方法的返回值是读取到的字节数量,如果最后一次读取的字节数量不足字节数组的大小时,只会将读取到的内容覆盖数组中最前的几个元素。所以最后一次读取时,读取的内容多于实际内容。
在通过FileOutputStream对象使用write(byte[] bytes),会将字节数组中的所有内容写入到输出流中,在最后一次写入时,会写入多余的内容。所以在写入时,使用write(byte[] bytes,int off,int len)方法,表示将字节数组中的内容,从off开始写入len个
单文件复制
//使用FileInputStream读取字节的同时使用FileOutputStream写入字节,实现单文件复制
File source = new File("d:\\xxx.txt");
//读取
FileInputStream fis = new FileInputStream(source);
//写入到目标文件中
FileOutputStream fos = new FileOutputStream("f:\\copy.txt");
//定义字节数组,大小为8MB
byte[]bytes=new byte[1024*1024*8];
//读取之ID你数组大小的字节,返回读取到的字节数量
int count=fis.read(bytes);
while (count>0) {
//写入指定数组的字节
//fos.write(int b);写入一个字节
//fos.write(byte[] bytes);写入一个数组的字节
fos.write(bytes,0,count);//写入一个数组的字节,从0开始写入读取到的数量的字节
count = fis.read(bytes);
}
fis.close();
fos.close();