Java学习笔记(五)

扶摇笑沧溟 2020-11-09 00:28:29
mark readline zos oos


2020-11-1

一、集合的流式处理

在JDK8以后,提供对集合的流式操作,对集合的元素可以向“流水”一样,依次方便,遍历,排序等,它是“不可逆的”(访问后面元素之后不能再次返回前面元素 ) , 根据流的处理方式不同,可以分为 串行流和并行流, 串行流表示同一时间只能有一个流式操作,而并行流可以有多个流式操作。

​ 流返回的结果包括中间操作和 最终操作

中间操作:它的返回值依然是 流对象 ,例如 排序、过滤、去重

最终操作: 返回值是特定的结果类型 ,例如 遍历,取最大值,最小值或返回新的集合

常用方法:

​ stream() :将一个集合流式化

​ filter(): 按条件过滤,里面使用lambda表达式

​ sort(): 排序集合元素

​ distinct: 过滤重复元素

​ reduce() : 将集合的所有元素累加或拼接

​ map(); 映射一个新的集合 对集合元素变更输出

​ collect():返回一个新集合

​ max() min():返回集合最大值或最小值

​ get (): 获取集合计算的结果

 public static void main(String[] args) {
List<Integer> list = new ArrayList();
for(int i = 0 ;i<7;i++){
list.add(i+1);
}
// 1、过滤 filter() 过滤掉偶数
list.stream().filter( param ->param%2==1 )
.forEach(System.out::println); //遍历元素
// 2、排序 sort() 降序
list.stream().sorted((o1,o2)->o2-o1).forEach(System.out::println);
// 3 map() 映射一个新的集合 , 如果是奇数 输出奇数 ,否则偶数
list.stream().map(
param -> param%2==1?"这个元素是奇数":"这是偶数"
).forEach(System.out::println);
list.add(1);
list.add(1);
System.out.println("去重元素");
// 4 distinct() 去除重复元素
list.stream().distinct().forEach(System.out::println);
// 5 reduce() 将集合的所有元素 累加(或拼接)
int sum = list.stream().reduce((o1,o2)->o1+o2).get();
System.out.println("总和:"+sum);
// 6 collect 返回一个新的集合
List<Integer> list2= list.stream().filter(param->param%2==1).collect(Collectors.toList());
System.out.println("遍历新集合");
list2.stream().forEach(System.out::println);
// 7、最大和最小
int max = list2.stream().max((o1,o2)->o1-o2 ).get();
System.out.println("最大值:"+max);
int min = list2.stream().min((o1,o2)->o1-o2 ).get();
System.out.println("最小值:"+min);
}

2020-11-2

一、Java的I/O

1、什么是I/O?

​ 在生活中,你需要将U盘的文件 ,拷贝到电脑(或者将电脑的文件拷贝到其他设备), 文件是通过数据流的方式依次到达另一个设备中, 文件的拷贝就是一个输入(Input)和输出(Output)的过程

Java中提供对应的API支持对文件的输入和输出 , java.io.*

2、什么是流?

生活中 也存在流的概念,例如 管道中的流水,从管道的入口到达管道出口,一滴水可以从入口流到出口,可以将“水”比作 “字节数据或字符数据”,数据也可以从一端流到另一端。

输入(Input): Java中,以“应用程序(内存)”为中心,将磁盘文件(设备端)到达内存中的过程 称为 输入

输出(Output): 以“应用程序(内存)”为中心,将数据从内存到达磁盘文件(设备端)的过程称为 输出

根据文件操作方式不同可以将IO分为三类

1、按照读写方向不同: 输入流(InputStream)和输出流(OutputStream)

2、按照数据类型不同: 字节流(Byte)和字符流(Char)

3、按照读写效率不同: 单一流和包装流(buffered等缓冲流)

关于流的分类Java提供 4个顶级抽象类 ,分布构建它们的子类

输入流 输出流
字节流 InputStream(字节输入流) OutputStream(字节输出流)
字符流 Reader(字符输入流) Writer(字符的输出流)

常见的流

  • 1、文件字节输入流和文件字节输出流 : FileInputStream 和 FileOutputStream

  • 2、文件字符输入流和文件字符输出流: FileReader 和 FileWriter

  • 3、缓存字节输入流和 缓存字节输出流 BufferedInputStream 和 BufferedOutputStream

  • 4、缓存字符输入流和缓冲字符输出流 BufferedReader 和BuffereWriter

  • 5、数据输入流和数据输出流: DataInputStream 和 DataOutputStream

  • 6、字节数组输入流 和 字节数组输出流 : ByteArrayInputStream 和 ByteArrayOutputStream

  • 7、字符数组输入流 和 字符数组输出流: CharArrayReader 和 CharArrayWriter

  • 8、转换流: (字节流转成字符流) InputStreamReader 和 OutputStreamWriter

  • 9、对象流(序列化流): ObjectInputStream 和 ObjectOutputStream

  • 10、随机访问流(这个流既可以读,也可以写):RandomAccessFIle

字节流

​ 定义: 文件的输入输出以一个“字节”为单位,进行流处理

FileInputStream 和 FileOutputStream

读入: 将文件中的数据读到内存中

常用方法:

​ int read() : 一个字节的读取 ,返回字节 的asci码 ,对于汉字会分3次读取

​ int read(byte) : 按一个数组长度读取, 返回实际读取的字节长度, 数据存放在数组中

​ int read(byte , offset , len) : 读取流中指定长度的数据,并存放在指定位置,

​ available() :返回流中剩余的字节长度,如果已读完,则返回0

​ skip(long n ): 丢弃指定的字节长度,从下一个开始读取

`**available**()`
 File file = new File("d:/aaa.txt");
FileInputStream fis = new FileInputStream(file);
//
byte [] b= new byte[10];
StringBuffer sb = new StringBuffer();
//每次读取的长度, b: 存放数据的数组
int len = 0;
while((len = fis.read(b)) !=-1){
sb.append( new String(b,0,len));
}
System.out.println(sb);
fis.close();
 public static void read2() throws IOException {
// InputStream是抽象类
InputStream is = new FileInputStream("d:/aaa.txt");
//丢弃前两个字节
is.skip(2);
System.out.println((char)is.read());
System.out.println("还剩下多少个字节:"+ is.available());
// 将后面的字节继续使用字节数组读
byte [] b = new byte[10];
int len = is.read(b,1,4);
// 显示数组中读取的所有数据
System.out.println(Arrays.toString(b));
//将数组的内容转成字符串 对于空内容不会转换
System.out.println(new String(b));
is.close();
}

文件写出: 将内存的数据写出到磁盘中

构造方法:

new FileOutputStream(File/String ) : 构造文件对象的写出流, 默认覆盖写出

new FileOutputStream(File/String , append): 构造文件对象的写出流,

append:表示在原有文件上追加数据, false : 覆盖

常用方法:

​ void write(int) : 写出一个字节

​ void writer(byte []) :写出一个字节数组,这里需要指定数组的编码格式 “UTF-8”

​ void writer(byte[] , offerset,len) : 写出一个字节数组,指定数组的长度和下标。 从数组的下标开始写出,len表示写出长度

​ flush() :清空缓存,对于使用缓冲流时,将缓冲强制清空。

 //将内存的数据写出到文件 如果文件不存在,会自动创建, 默认覆盖写入 true:追加
FileOutputStream fos = new FileOutputStream("d://aaa.txt" ,true);
String str="今天天气还不错";
fos.write(99);
//写出一个字符串 字符串可以转成字节数组 或字符数组
fos.write(str.getBytes("UTF-8"));
// 写出指定长度
fos.write(str.getBytes("UTF-8"),0,3); // 写出这个数组的前2个字节
// 清空缓存
fos.flush();
// 关闭流
fos.close();
System.out.println("写出成功");

文件复制:

将文件(图片,文本,视频)从一个目录复制到另一个目录, 其中数据长度不变,通过文件读写的方式完成复制

复制过程:从源文件读取数据,然后将数据再出到目标文件中。

/**
* 单个字节复制
* @param srcFile 源文件
* @param disFile 目标文件
*/
public static void copyFile(File srcFile, File disFile){
FileInputStream fis=null;
FileOutputStream fos =null;
try {
// 源文件输入流
fis = new FileInputStream(srcFile);
// 目标文件输出流
fos = new FileOutputStream(disFile);
int n=0;
while( (n =fis.read()) !=-1){
//将读到的n写出到 目标文件中
fos.write(n);
}
System.out.println("复制成功。。");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
//无论是否发生异常 都会关闭流
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 一个字节数组的赋值
* @param src 源地址
* @param disc 目标地址
*/
public static void copyFile(String src,String disc){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建 字节输入流
fis=new FileInputStream(src);
fos = new FileOutputStream(disc);
int len=0;
byte [] b = new byte[1024];
while( (len= fis.read(b)) !=-1){
// 写出 实际读取的长度 ,为了避免在最后一次写出时出现多余字节
fos.write(b,0,len);
}
System.out.println("复制成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

练习一: 将d://mywork目录下的文件 赋值到 e://mywork ​ // 其中 目录下包含子目录 也按照相同的文件目录复制 ​ // 思路: 1、先遍历文件夹,递归调用, ​ // 如果是文件先获取文件的父路径,如果不存在,就创建 ​ // 如果存在路径就直接复制文件(调用刚才的赋值方法) ​ // 2、 创建父路径使用 mkdirs() ​ // 例如 d://mywork/aaa/1.txt ​ // 目标: e://mywork/aaa/1.txt 可以使用字符串替换 d:替换e: ​ // 先看这个路径是否存在,如果不存在 就创建

字符流

​ 字符流用于读写存储字符的文件, 以一个字符为单位,一依次读取字符 文件, 常用类以 Reader或Writer为父类, 对文件的操作使用 java.io.FileReader 和java.io.FileWriter

​ 读文件 :FileReader

常用方法:

​ new FileReader(path): 通过文件路径构建字符输入流

​ new FileReader(File):通过文件对象构建字符输入流

  • ​ int read() :读取一个字符 ,返回字符的int类型

  • ​ int read(char ):读取字符数组长度的数据 ,返回实际读取字符长度,数据存放在字符数组中

  • ​ int read(char offerset len):读取指定字符长度的数组,返回 实际读取字符的长度,数据存放在字符数组中

  • ​ mark(int) :标记流中当前位置 (读取到哪里了)

  • ​ markSupported():判断此流是否支持mark操作

  • ​ reset(): 重置流数据,(又可从头开始读取)

  • ​ skip(long) :丢弃指定长度字符

读字符文件

 // 1、创建字符输入流
try {
FileReader reader = new FileReader("d:/myfile.txt");
// 丢弃字符
reader.skip(1);
//读一个字符
System.out.println((char)reader.read());
System.out.println((char)reader.read());
//读一个字符数组长度
char [] c = new char[10];
System.out.println("实际长度:"+reader.read(c));
System.out.println(new String(c));
//继续读
int len = reader.read(c,0,5);
System.out.println("字符数组:"+ Arrays.toString(c));
System.out.println("读指定长度字符个数:"+new String(c,0,len));
// 将字符流重置
// reader.reset();
// System.out.println("重置后继续读:"+ reader.read());
//System.out.println("是否支持标记字符:"+reader.markSupported());
//关闭流,后面就不能使用该对象
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

写文件: 将内存数据写出到文件中,在写出过程中可以 覆盖写出也可以追加写出,FileWriter类创建对象过程

​ new FileWriter(String ):指定写出文件地址

​ new FileWriter(String ,append) : 指定写出文件地址,设置是否追加写出,true表示追加,false表示覆盖

​ new FileWriter(File)指定写出文件对象

​ new FileWriter(File ,append);指向写出文件对象,设置是否可追加

常用方法:

​ writer(int) :写出一个字符

​ writer(String):写出一个字符串

​ writer(char [] ):写出一个字符数组

​ writer(char [] , offerset , len):写出一个指定长度的字符数组

​ flush() :刷新缓冲,

​ close():关闭缓冲

​ append(c) :将指定字符添加到此流中

 // 1、创建文件写出流 FileWriter
try {
// 文件不存在,可自动创建,但是不会创建目录
File file = new File("d://myabc/aaa.txt");
//判断文件目录不存在, 先创建目录
if(!file.getParentFile().exists()){
//创建该目录
file.getParentFile().mkdirs();
}
FileWriter writer = new FileWriter("d://myabc/aaa.txt");
// 写一个字符的asci
writer.write(98);
//写字符串
writer.write("hello");
//写指定长度的字符串
writer.write("abcdef",0,3); //写abc
char [] c = {'L','O','L'};
//写字符数组
writer.write(c);
System.out.println("写出成功");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}

关闭和刷新:

对于带有缓冲 功能的写出流,需要先刷新缓冲区,才能将数据写出,如果不刷新则最后不能正常写出。写出流如果刷新后还可以继续写,而关闭了则不能继续写。

面试题 flush 和close的区别?

flush: 刷新缓冲 ,流可以继续使用

close: 先刷新缓冲器,然后再释放系统资源, 关闭后不能继续使用

 try {
FileWriter writer = new FileWriter("1.txt");
writer.write("刷");
writer.flush();
writer.write("新");
writer.flush();
writer.write("关");
writer.close();
writer.write("闭"); // 这里抛出异常 , Stream closed
writer.close();
} catch (IOException e) {
e.printStackTrace();
}

关于换行符

回车符 \r 和换行符 \n : 回车符:回到一行的开头(return)。 换行符:下一行(newline)。 系统中的换行: Windows系统里,每行结尾是 回车+换行 ,即 \r\n ; Unix系统里,每行结尾只有 换行 ,即 \n ; Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。


*2020-11-3

一、包装流

定义: 在原始字节流或字符流的基础性,为了提高读写效率进行再次处理的流, 称为包装流/处理流

1、缓存字节流 BufferedInputStream 、BufferedOutputStream

​ 由于原始流在文件读写时 效率比较低(操作文件本身占用资源较多),可以通过创建缓冲区的方式提高读写效率, 将读取/写出的数据线放入缓冲区,到达一定数量后再次冲缓冲区读取/写出

​ mark(readLimit) 与 reset()用法

其中reset不能单独使用,必须mark(readLimit) ,readLimit表示标记后最多读取的上限,但是这里标记后读取的内容与BufferedInputStream的缓冲大小有关,比由上限决定,也就是说读取的内容超出上限可以继续重置到mark的位置。

public static void main(String[] args) throws IOException {
//创建缓冲流
InputStream is = new FileInputStream("d:/myfile.txt");
BufferedInputStream bis = new BufferedInputStream(is);
//是否支持mark 或 reset
System.out.println(bis.markSupported());
System.out.println((char)bis.read());//97
//重置
bis.mark(3); // pos标记往后退三个 最多可以读取字节上限
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
bis.reset(); // 这里 重置后 退回到3个以前的位置
// 重置后输出
int n =0;
while( (n = bis.read()) !=-1){
System.out.println("重置后;"+(char)n);
}
//关闭流
bis.close();
is.close();
}

2、缓存字符流 (BufferedReader 、BufferedWriter)

 public static void main(String[] args) throws IOException {
// 缓冲字符流 可以一行一行读取 、写出
BufferedReader br = new BufferedReader(new FileReader("d:/小众网站.txt"));
//读一行
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
String s = null; //读的数据为空 则不需要读
while( (s = br.readLine()) !=null){
System.out.println(s);
}
br.close();
//缓冲写出流
FileOutputStream pw = new FileOutputStream("d:/abcd.txt");
//由于字节流不能直接放入 字符缓冲区,需要将它转成字符流 使用转换流并可以指定编码格式
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pw));
bw.newLine();// 开启新一行(换行)
bw.write("这是测试转换流的方式");
bw.close();
}

3、打印流(输出流) PrintWriter 、PrintStream

 public static void main(String[] args) throws FileNotFoundException {
// 打印流 ,提供一些打印输出方法
PrintWriter pw = new PrintWriter("d:/abcd.txt");
pw.print(100);
pw.println('a');//换行打印
pw.print("hello");
pw.close();
//System.out 字节打印流 PrintStream

4、数据字节流DataInputStream、DataOutputStream

它们用于读入写出Java基本数据类型的数据到文件或其他设备端,它们也属于包装流

DataOutputStream 常用方法

  • ​ writerByte(byte):写一个字节到设备或文件

  • ​ writerChar(char):写一个字符到设备或文件

  • ​ writerInt(int):写一个4个字节的int到设备或文件

  • ​ writer(boolean):写一个boolean类型到设备或文件

  • ​ writerDouble(double):写一个double类型到设备或文件

  • ​ writerFloat(float):写一个float类型到设备或文件

  • ​ writerLong(long):写一个long类型到设备或文件

  • ​ writerShort(short):写一个short类型到设备或文件

  • ​ writerUTF(String):写一个字符串类型到设备或文件

DataInputStream: 读指定文件的数据,可以读数据类型

  • ​ int readInt() :读一个int类型

  • ​ short readShort():读一个short类型

  • readByte():读一个字节类型

  • ​ read():读一个字节类型

  • ​ readDouble(): 读一个double类型

  • ​ readFloat():读一个float类型

  • ​ readChar():读一个字符类型

  • ​ readBoolean():读一个boolean类型

  • ​ readLong() :读一个long类型

 public static void main(String[] args) throws IOException {
//创建数据写出流
DataOutputStream dos = new DataOutputStream(
new FileOutputStream("d:/data.txt"));
//写一个int类型 依次写出4个字节
dos.writeInt(100);
dos.writeBoolean(true);
//关闭
dos.close();
//读取文件 创建数据读入流 ,需要按写的顺序读进来
DataInputStream dis = new DataInputStream(
new FileInputStream("d:/data.txt"));
//读一个int类型 (依次读4个字节)
int num = dis.readInt();
System.out.println("读取的数据:"+ num);
System.out.println("读的数据:"+dis.readBoolean());
dis.close();
}

5、转换流

​ 转换流是将字节流转成字符流的桥梁, 也可以在转换时指定编码格式。 InputStreamReader 和 OutputStreamWriter

 public static void main(String[] args) throws IOException {
// 字节流转成字符流
InputStream is = new FileInputStream("d://小众网站.txt");
InputStreamReader isr = new InputStreamReader(is);
//缓冲流 读取数据
BufferedReader br = new BufferedReader(isr);
//读一行
String str =null;
while( (str= br.readLine()) !=null){
System.out.println(str);
}
//关闭流
br.close();
isr.close();
is.close();
}
 public static void main(String[] args) throws IOException {
// 创建 字节转成字符的 写出流 FileOutputStream os =
FileOutputStream fos = new FileOutputStream("d://data.txt");
//指定编码 GBK 格式一个汉字占2个字节 UTF-8 格式一个汉字占3个字节
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
//缓冲形式的
BufferedWriter bw = new BufferedWriter(osw);
bw.write("你好");
bw.newLine();
bw.write("我不好");
bw.close();
}

6、随机字节流

RandomAccessFile 是随机字节流,它是一个可读可写的流 ,在文件操作时指定该对象的模式(model)后,可以读数据或写数据

实现 DataInputStream和DataOutputStream类

构造器:

RandomAccessFile rm = new RandomAccessFile(File ,mode);

​ RandomAccessFile rm = new RandomAccessFile(String ,mode);

mode表示对象的模式

r: 表示该对象只能读 不能写

rw/rws/rwd :表示该 对象是可读可写的;

public static void main(String[] args) throws IOException {
//创建可读 的流
RandomAccessFile reader = new RandomAccessFile("d://data.txt","r");
//创建可读可写的 的流
RandomAccessFile writer = new RandomAccessFile("d://data-1.txt","rw");
// 读和写的过程和之前一样
byte [] b= new byte[10];
int len=0;
while( (len = reader.read(b)) !=-1){
writer.write(b , 0 , len);
}
System.out.println("复制成功");
//关闭流
writer.close();
reader.close();
}

skipByte 和 seek的区别

 // 跳字节读取
RandomAccessFile raf = new RandomAccessFile("d:/data.txt","rw");
// 跳过2个字节
raf.skipBytes(2);
System.out.println((char)raf.readByte()); //3
System.out.println("当前偏移量:"+raf.getFilePointer());//3
// 又丢弃1个字节 从当前位置 往后偏移1位
raf.skipBytes(1);
System.out.println("修改后的偏移量"+raf.getFilePointer());//4
System.out.println("偏移后的读取数据:"+(char)raf.readByte()); //5
raf.close();
// seek用法
RandomAccessFile raf2 = new RandomAccessFile("d:/data.txt","rw");
// 设置当前读取的位置 ,从0开始计算 ,指定n ,就从n的下一个字节 读取
raf2.seek(2);
System.out.println("seek后的数据:"+(char)raf2.readByte());//3
raf2.seek(1); // 又从0开始 设置偏移量为1 
System.out.println("修改后的偏移量"+raf.getFilePointer());//1
System.out.println("seek后的数据:"+(char)raf2.readByte())//2
raf2.close();

7、对象序列化流

​ 对象流也称为序列化流,用于存储对象和读取对象的字节流,也是属于包装流

序列化和反序列化

​ 将内存中的对象(Object,集合类等)保存到磁盘、网络介质、其他设置的过程,并在合适的时间能获取磁盘文件/网络的数据 ,这个过程就是对象的序列化和反序列化。

为什么需要序列化和反序列化呢?

​ 在之前文件中存储的文本信息,这样不便于对数据的分类和操作,如果可以做到直接对对象的读和写这样可大大提高编程效率,并最大程度保证对象的完整性。

Java-IO中实现对象序列化的两种方式:

  1. 实现Serializable接口

  2. 实现Externalizable接口

    Serializable接口

    对象需要实现该接口,但是它没有任何需要实现的方法,只有一个用于标记该类可序列化的唯一标识。 任何类需要序列化都必须标记该变量

    public class User implements Serializable {
    // 对于能区分其他类的唯一表示
    private static final long serialVersionUID = 1L;
    private int uid;
    private String name;
    private String password;
    // 有一部分属性不能序列化
    public int getUid() {
    return uid;
    }
    public void setUid(int uid) {
    this.uid = uid;
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public String getPassword() {
    return password;
    }
    public void setPassword(String password) {
    this.password = password;
    }
    @Override
    public String toString() {
    return "User{" +
    "uid=" + uid +
    ", name='" + name + '\'' +
    ", password='" + password + '\'' +
    '}';
    }
    }
    
     //创建序列化的对象流 从内存到文件
    ObjectOutputStream oos = new ObjectOutputStream(
    new FileOutputStream("d:/user.txt"));
    User user= new User();
    user.setUid(1001);
    user.setName("admin");
    user.setPassword("123456");
    //序列化对象
    oos.writeObject(user);
    //关闭流
    oos.close();
    // 反序列化: 将文件中的数据 再读入到内存中 ,需要一个读的流 ObjectInputStream
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://user.txt"));
    // 反序列化尽量只读一次 (也可以读多次, 如何写出就如何读入)
    Object obj = ois.readObject();
    if(obj instanceof User){
    User u = (User)obj;
    System.out.println("反序列化的结果:"+u);
    }
    //关闭流
    ois.close();
    

​ 问题: 能否自定义序列化的属性 ,这里可以采用方式二,实现Externalizable,并重写两个方法 接口继承而来,在其基础上新增了两个未实现方法:readExternal(ObjectInputStream)和 writeExternal(ObjectOutputStreawm) ,自定义需要序列化的属性

public interface Externalizable extends java.io.Serializable

Externalizable接口

public class Student implements Externalizable {
private int id;
private String name;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
// 自定义可序列化的属性
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(this.id);
out.writeUTF(this.name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readInt();
this.name = in.readUTF();
}
public Student(int id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
public Student( ) {
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
 public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建序列化类
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:/stu.txt"));
//创建学生
List<Student> list = new ArrayList<>();
list.add(new Student(1001,"张飞","男"));
list.add(new Student(1002,"刘备","男"));
list.add(new Student(1003,"小乔","女"));
// 将集合序列化
oos.writeObject(list);
//关闭
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("d:/stu.txt"));
//读
Object obj = ois.readObject();
if(obj instanceof List){
List<Student> list2 = (List<Student>)obj;
for(Student s : list2){
System.out.println(s);
}
}
//关闭流
ois.close();
}

​ 问题: 哪些属性不能实现序列化

​ * 1、类中的static修饰的属性不能序列化

​ 2、类中属性被transient修饰的不能序列化 例如 transient private Integer age = null;

​ 3、实现Externalizable接口的类的属性不能全部序列化,必须手动写可序列化的属性。


2020-11-4

一、文件的压缩流和解压流

1、为什么需要使用压缩文件

文件压缩使用场景: 在文件上传或下载中需要操作多个文件时,如果一个一个复制需要花较长时间,而且比较繁琐,javaAPI提供一种压缩/解压文件方式,可以将多个文件打包成一个文件(.zip)

包: java.util.zip

常用类: ZipEntry: 表示压缩文件中的每一个实体文件

​ ZipFile: 表示压缩文件对象

​ ZipOutputStream: 表示压缩文件输出流,用于将普通文件写出到zip文件中

​ ZipInputStream: 表示解压文件的输入流,用于读zip文件中的每一个实体ZipEntry 这里的abcd.txt就是一个ZipEntry

2、压缩文件步骤

a、创建需要压缩文件的输入流(InputStream )

​ b、创建压缩包所在的路径,并指定压缩文件名,同时创建ZipOutputStream输出流

​ c、将文件对象 添加到 ZipOutputStream中的实体中(也可以指定压缩后的实体名称)

​ d、文件复制

​ e、关闭流

 public static void main(String[] args) throws IOException {
// 1、创建文件对象
File file = new File("d:/小众网站.txt");
// 2、创建文件的输入流
FileInputStream fis = new FileInputStream(file);
// 3、创建文件压缩流(输出流)
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry("小众网站.txt "));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
zos.close();
fis.close();
}

压缩多个文件

 /**
* 压缩一个文件夹 myfile
* @param args
*/
public static void main(String[] args) throws IOException {
//构建压缩包的输出流
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
File file=new File("d:/myfile");
File [] files = file.listFiles();
for(File f : files){
//构造每一个文件的输入流
FileInputStream fis = new FileInputStream(f);
putZipFile(fis, zos ,f.getName());
System.out.println(f.getName()+"文件压缩成功" );
}
//关闭压缩流
zos.flush();
zos.close();
}
/**
* 将文件放入压缩流中
* @param fis
* @param zos
* @param entryName
* @throws IOException
*/
public static void putZipFile(InputStream fis ,
ZipOutputStream zos,
String entryName) throws IOException {
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry(entryName));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
fis.close();
}

3、解压文件步骤

​ 解压文件是将一个.zip文件的内容,复制到文件下,需要使用ZipInputStream

​ 解压文件的关键点: 获取解压文件的每一个条目ZipEntry的输入流 ,将输入流写出到指定位置。

​ 如何获取输入流: ZipFile对象 表示一个zip文件 步骤:

​ a、根据文件路径 创建ZipInputStream

​ b、根据文件路径创建ZipFile对象

​ c、循环遍历每一天条目, 得到它的ZipEntry

​ d、获取ZipEntry的输入流

​ e、将文件复制到指定位置

 public static void main(String[] args) throws IOException {
//1、创建ZipInputStream
ZipInputStream zis = new ZipInputStream(
new FileInputStream("d:/myfile.zip"));
// 2、创建ZipFile对象
ZipFile zipFile = new ZipFile("d:/myfile.zip");
// 3、获取zip中的实体
ZipEntry en = null;
while( (en= zis.getNextEntry())!=null){
System.out.println(en.getName()+"--"+en.isDirectory());
//4、获取每一个en的输入流 (关键)
InputStream is = zipFile.getInputStream(en);
copyFile(is ,"d:/my",en.getName());
}
}
/**
* 通过输入流 复制文件到指定的目录下
* @param is 输入流
* @param path 存放文件的路径
* @param fileName 文件名
*/
public static void copyFile(InputStream is , String path , String fileName) throws IOException {
File file = new File(path);
//判断目录是否存在, 不存在就 创建
if(!file.exists()){
file.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path+File.separator+fileName);
int len = 0 ;
byte [] b = new byte[1024];
while( (len = is.read(b)) !=-1){
fos.write(b,0,len);
}
System.out.println("解压成功");
fos.close();
is.close();
}

二、Java的多线程

1、线程的基本概念

​ 1.1 定义

​ 引入线程: 打开计算中的任务管理器,有很多条目,每一条目对应一个应用程序,这个应用程序我们称之为 “进程” ,每一个进程都占用CPU资源和内存, 在这一个进程中 包含多个任务,他们可以“同时”运行, 这里的每一个任务称为”线程“

​ 如果将Java的 应用程序比作一个进程,那么它包含的多个执行流程就是一个 线程。

​ 生活中的多线程: 你现在正在玩游戏 ,你可以一边聊天(互喷),你也可以操控游戏,还可以问候队友。玩游戏就是一个进程,你的不同的操作对于游戏本身就是一个单线程,如果你可以同时操作,就是游戏可支持 多线程。

​ 进程:进程是计算机中独立的应用程序,进程是动态,可运行的

​ 线程:在进程中运行的单一任务,是进程的子程序

​ 程序: 程序是数据描述和操作代码的集合,它是完成某个功能的代码,它是静态的 多线程: 一个进程中的多个子任务, 在多线程中会出现资源抢占问题, 在单核CPU下同一时间点某个进程下只能有一个线程运行。线程与线程之间会互抢资源

CPU资源分配 电脑可以运行多个应用程序(多进程),在同一时间片,CPU只能执行某一个任务,由于时间片切换非常快,你根本不能察觉会出现“等待”的情况,如果电脑出现 “卡死” 你可以任务资源没有获取并正在等待中。

单线程运行流程:程序只有一条运行线路,从开始到结束保持一致 多线程:可以有多条结束任务,对于那一条先结束无法预知 如何创建多线程的程序呢?

方式一: 继承Thread类

a、 定义一个类继承Thread类,重写run方法

b、创建该类的对象, 并调用start方法

public class MyThread extends Thread {
@Override
public void run() {
for(int i=0;i<100;i++){
//获取当前线程名
System.out.println(this.getName()+"----"+i);
}
}
}
 public static void main(String[] args) {
// 创建线程对象
MyThread my = new MyThread();
//开启线程
my.start();
for(int i = 0 ;i < 100 ;i ++){
System.out.println("主线程的 i-----"+i);
}
// 结论: 对于多线程之间它们的执行过程会存在资源抢占,谁先获得cpu资源,谁就执行
}

方式二:实现Runnable接口

a、创建一个类实现一个接口

public class MyThread2 implements Runnable {
@Override
public void run() {
for(int i = 0;i<100;i++){
//获取当前 线程的线程名
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}

b、借助Thread类开启线程

 public static void main(String[] args) {
// 由于 MyThread2 与线程无关联,需要借助线程类完成启动
// 创建线程需要执行的任务类
MyThread2 my = new MyThread2();
Thread th = new Thread(my,"线程A");
th.start();
//再启动一个
Thread th2 = new Thread(my,"线程B");
th2.start();

问题:以上两种创建线程的区别?

1、继承方式适用于没有直接父类 ,相对简单 ,是单一继承, 而接口的方式目标类既可以继承类还可以实现其他接口

2、Runnable实现方式适用于 资源共享,线程同步情况。

3、Runnable实现方式并不是线程类,而是实现线程的目标类(Target)

补充: 创建线程并非只有以上两种方式,还可以通过匿名内部的方式创建线程和 线程池的方式。

2、线程的生命周期

生命周期定义

线程从创建到销毁的整个过程,称为线程生命周期, 好比人的生命周期就是从出生到去世的整个过程中间会经历的过程包括 出生,长大,变老,离开 都是一个人要经历的。

生命周期的阶段

1、新生状态 : 程序创建该线程(实例化对象)

2、就绪状态(可运行状态) : 当线程对象调用start()方法后 ,可以抢占cpu资源,但不会立马运行run方法

3、运行状态: 当抢占到资源后,立马运行run方法

4、阻塞状态: 在运行过程中,线程遇到阻塞事件(线程休眠,wait ,IO操作,join操作等),变为阻塞状态

5、死亡状态: 线程运行完毕,或异常中断 ,此时CPU资源被释放

版权声明
本文为[扶摇笑沧溟]所创,转载请带上原文链接,感谢
https://my.oschina.net/Yinux/blog/4708425

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云