零、前言
$$$ 学好IO流必须要会做的8大需求
01.使用FileInputStream + FileOutputStream 配合一个大数组 完成文件复制
核心代码:
FileInputStream fis = new FileInputStream("源文件");
FileOutputStream fos = new FileOutputStream("目标文件");
byte[] data = new byte[65536];
int len;
while((len = fis.read(data))!=-1){
fos.write(data,0,len);
}
fos.close();
fis.close();
TWR:
try(FileInputStream fis = new FileInputStream("源文件");
FileOutputStream fos = new FileOutputStream("目标文件")){
byte[] data = new byte[65536];
int len;
while((len = fis.read(data))!=-1){
fos.write(data,0,len);
}
}catch(Exception e){
e.printStackTrace();
}
02.使用BufferedInputStream + BufferedOutputStream 不用数组 一次一个字节的完成文件复制
核心代码:
FileInputStream fis = new FileInputStream("源文件");
BufferedInputStream bis = new BufferedInputStream(fis,5<<20);
FileOutputStream fos = new FileOutputStream("目标文件");
BufferedOutputStream bos = new BufferedOutputStream(fos,5<<20);
int len;
while((len = bis.read())!=-1){
bos.write(len);
}
bos.close();
bis.close();
TWR:
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("源文件"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("目标文件"))){
int len;
while((len = bis.read())!=-1){
bos.write(len);
}
}catch(Exception e){
e.printStackTrace();
}
03.将内存当中一个基本数据类型的变量,例如 long level = 368 保存到磁盘文件当中
核心代码:
FileOutputStream fos = new FileOutputStream("数据.data");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeLong(level);
dos.close();
TWR:
try(DataOutoputStream dos = new DataOutputStream(new FileOutputStream("save.data"))){
dos.writeLong(num);
}catch(Exception e){
e.printStackTrace();
}
04.从文件当中读取一个基本数据类型的变量到程序当中去
核心代码:
FileInputStream fis = new FileInputStream("数据.data");
DataInputStream dis = new DataInputStream(fis);
long ok = dis.readLong();
dis.close();
TWR:
try(DataInputStream dis = new DataInputStream(new FileInputStream("save.data"))){
long x = dis.readLong();
}catch(Exception e){
e.printStackTrace();
}
05.将内存当中一个引用类型的对象 例如 Teacher tea = new Teacher("JayZhou",36) 保存到文件当中去 *:注意要序列化serializable
核心代码:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("电冰箱.data"));
oos.writeObject(tea);
oos.close();
TWR:
try(ObjectOutputStream oos = new ObjcetOutputStream(new FlieOutputStream("sava.data"))){
oos.writeObject(tea);
}catch(Exception e){
e.printStackTrace();
}
06.从文件当中取一个引用类型的对象到程序当中
核心代码:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("电冰箱.data"));
Object obj = ois.readObject();
ois.close();
Teacher tea = (Teacher)obj;
TWR:
try(ObjectInputStram ois = new ObjectInputStrem(new FileInputStream("sava.data"))){
Object obj = ois.readObject()
}catch(Exception e){
e.printStackTrace();
}
07.以指定的编码逐行的写出文本文件
核心代码:
PrintWriter pw = new PrintWriter("test.txt","utf-8");
pw.println("东临碣石 以观沧海");
pw.println("水何澹澹 山岛竦峙");
pw.close();
TWR:
try(PrintWriter pw = new PrintWriter("test.txt","utf-8")){
pw.println("zzzzzzzzzz");
}catch(Exception e){
e.printStackTrace();
}
08.以行为单位读取指定编码的文本文件
核心代码:
FileInputStream fis = new FileInputStream("test.txt");
InputStreamReader r = new InputStreamReader(fis,"utf-8");
BufferedReader br = new BufferedReader(r);
String str;
while((str = br.readLine())!=null){
System.out.println(str);
}
br.close();
09.文件下载(原理就是文件的复制)
核心代码:
URL url = new URL("xxxx");
URLConnection uc = url.openConnection();
InputStream is = uc.getInputStream();
FileOutputStream fos = new FileOutputStream("my.jpg");
byte[] data = new byte[16384];
while((len = is.read(data))){
fos.write(data,0,len);
}
fos.close();
is.close();
$$$ 学好文件流必须学会的几大方法
包:java.io.File
构造方法:
new File(String 路径)
new File(String 父目录,String 文件名)
new File(File 父目录对象,String 文件名)
*: 绝对路径 or 相对路径
*: File.separator
特等优先级方法
static listRoots() : 列出当前计算机的所有根目录
String[] list() : 列出一个目录当中所有的文件名字
File[] listFiles() : 列出一个目录当中所有的文件对象
*: 文件属性过滤器 FileFilter
public boolean accept(File file){
..;
}
一等优先级方法 12个
4 exists() isFile() isDirectory() length()
3 delete() mkdirs() renameTo()
3 getName() getParent() getAbsolutePath()
2 setLastModified() lastModified()
Java当中如何解析时间戳
java.util.Date
getYear()+1900 getMonth()+1 getDate()
getHours() getMinutes() getSeconds()
java.util.Calendar
getInstance()
setTimeInMillis()
get(x)
1 2+1 5 11 12 13 7-1
java.text.SimpleDateFormat
format() : 从long 时间戳 到 String
parse() + getTime() : 从String到long时间戳
获取系统当前时间
System.currentTimeMillis()
一、文件
1.1 导包
java.io.File
1.2 构造方法
1.2.1 new File(String 路径)
- 相对路径
- 相对路径:从程序认定的主目录出发(.class) 定位我们要找的文件,例如主目录下存在
hello.txt
,那我们可以这样写new File("hello.txt")
- 相对路径:从程序认定的主目录出发(.class) 定位我们要找的文件,例如主目录下存在
- 绝对路径
- 绝对路径:从盘符或者根目录出发 定位我们要找的文件,例如桌面上的
hello.txt
,我们可以这样写new File("C:\Users\WangFan\Desktop\hello.txt")
- 绝对路径:从盘符或者根目录出发 定位我们要找的文件,例如桌面上的
- File.separator 路径分界符
- 为了实现java跨平台特性, File.separator 路径分界符应运而生,例如
hello.txt
父目录为文件夹abc,可以这样写File flie = new File("abc" + File.separator + "hello.txt");
- 为了实现java跨平台特性, File.separator 路径分界符应运而生,例如
1.2.2 new File(String 父目录,String 文件名)
- 例如
hello.txt
父目录为文件夹abc,可以这样写File flie = new File("abc","hello.txt");
1.2.3 new File(File 父目录对象, String 文件名)
-
例如
hello.txt
父目录为文件夹abc,abc的父目录为ABC,可以这样写File flie1 = new File("ABC","abc"); File flie2 = new File("flie1","hello.txt");
1.2.4 综合练习
-
挖掘机技术哪家强 中国山东找蓝翔 蓝翔里面有小明 请用兼容性最好的方式定位小明.exe
//方式一 File file1 = new File("中国/山东/蓝翔/小明.exe"); //方式二 File file2 = new File("中国"+File.separator+"山东"+File.separator+"蓝翔"+File.separator+"小明.exe"); //方式三 StringBuffer buff = new StringBuffer("中国"); buff.appned(File.separator).append("山东"); buff.appned(File.separator).append("蓝翔"); buff.appned(File.separator).append("小明.exe"); File file3 = new File(buff.toString()); //方式四 File file4 = new File(new File(new File(new File("中国"),"山东"),"蓝翔"),"小明.exe"); //方式五 File zg = new File("中国"); File sd = new File(zg,"山东"); File blueFly = new File(sd,"蓝翔"); File littleM = new File(blueFly,"小明.exe");
1.3 一等优先级的方法(12种)
1.3.1 只读操作
-
exists() : 判断File对象代表的
文件
或者目录
是否已经存在,File对象既可以代表已经存在的文件或者目录
,又可以代表尚不存在的文件或者目录
,所以需要方法来进行判断校验 -
isFile() : 判断File对象代表的是不是一个文件,File对象
既可以代表一个文件,又可以代表一个目录
,所以需要方法进行判断 -
isDirectory() : 判断File对象代表的是不是一个目录,File对象
既可以代表一个文件,又可以代表一个目录
,所以需要方法进行判断 -
length() : 得到文件的字节个数,它返回的是
long
不是int,它只能对文件进行调用
,不能对目录进行调用
,否则将得到非预期结果…,例如返回0 -
方法或者属性 length length() size() length() 种类 数组,数据长度 字符串,字符串长度 集合,集合大小 文件 数据类型 int int int long
//测试exists()方法
import java.io.*;
public class TestExists{
public static void main(String[] args){
File file1 = new File("focus.txt");
System.out.println(file1.exists());//true
File file2 = new File("sucof.txt");
System.out.println(file2.exists());//flase
}
}
//测试isFile() & isDirectory()方法
import java.io.*;
public class TestIsSth{
public static void main(String[] args){
File f1 = new File("focus.txt");
System.out.println(f1.isFile());//true
System.out.println(f1.isDirectory());//false
File f2 = new File("周府");
System.out.println(f2.isFile());//false
System.out.println(f2.isDirectory());//true
}
}
//测试length()方法,只能对文件使用,对目录使用会出现0
import java.io.*;
public class TestLength{
public static void main(String[] args){
File file = new File("周府");
System.out.println(file.length());//0
File file1 = new File("focus.txt");
System.out.println(file1.length());//2688
}
}
1.3.2 写入操作
-
delete() : 删除File对象代表的文件或者目录,注意该删除不经过回收站,直接删,
注意如果要删除的是个目录,则必须保证目录是空的,否则删除失败
-
mkdirs() :
make directories = mkdirs
创建多层不存在的目录结构 =建目录的
,注意 File类还有个mkdir() 方法 ,只能创建一层不存在的目录结构 -
renameTo() : 重命名文件或者目录
*: a.renameTo(c) a源文件 必须已经存在 c目标文件 必须尚不存在 *: 注意源文件和目标文件可以是不同的目录结构,从而实现移动剪切 在Java中,方法 `renameTo()` 是用于重命名文件的方法。参数 `c` 是用于指定新的文件名的参数。该参数可以是一个有效的文件路径,也可以是一个表示新文件所在目录的 `File` 对象。 要理解这个参数是否可以存在,需要考虑以下几个方面: 1. 文件路径的有效性:参数 `c` 必须是一个有效的文件路径。这意味着它应该包含合法的文件名,文件名中不包含特殊字符或非法字符,并且指定的文件路径在文件系统中是可访问的。 2. 文件名冲突:如果参数 `c` 指定的文件名已经存在,`renameTo()` 方法可能会返回 `false`,表示重命名操作失败。在这种情况下,你可能需要选择一个不同的文件名或路径来避免冲突。 3. 目录权限:如果参数 `c` 是一个 `File` 对象,表示新文件所在的目录,那么你需要确保当前用户对该目录具有足够的权限,以便在该目录下进行文件重命名操作。 因此,确保参数 `c` 的合法性、避免冲突以及保证目录访问权限是使用 `renameTo()` 方法时需要考虑的一些因素。如果这些条件都得到满足,那么参数 `c` 是可以存在的。
//测试delete方法,删除delete.txt文件
import java.io.*;
public class TestDelete{
public static void main(String[] args)throws Exception{
Thread.sleep(5000);
File file = new File("delete.txt");
file.delete();
}
}
//测试mkdirs方法,创建目录C:\Users\WangFan\Desktop\etoak\吴威课程\每日课件\day06 [File]\0\1\2
import java.io.*;
public class TestMkdirs{
public static void main(String[] args){
StringBuffer buff = new StringBuffer();
for(int i = 0;i<3;i++){
buff.append(i).append(File.separator);
}
File dir = new File(buff.toString());
dir.mkdirs();
}
}
//测试renameTo方法
import java.io.*;
public class TestRenameTo{
public static void main(String[] args)throws Exception{
Thread.sleep(5000);
//相当于剪切
File src = new File("周府","1.txt");
File tar = new File("2.txt");
src.renameTo(tar);
//相当于重命名
File src1 = new File("2.txt");
File tar1 = new File("3.txt");
src1.renameTo(tar1);
}
}
1.3.3 “get”得到操作
- getName() : 得到文件或者目录的名字
- getParent() : 得到文件或者目录的父目录
- getAbsolutePath() : 得到文件或者目录的绝对路径
import java.io.*;
public class TestGetSth{
public static void main(String[] args){
File file = new File("abc/cba/focus.txt");
System.out.println(file.getName());//focus.txt
System.out.println(file.getParent());//abc\cba,此处得到的就是/focus.txt前面所有的一大串东西
System.out.println(file.getAbsolutePath());//C:\Users\WangFan\Desktop\etoak\吴威课程\每日课件\day06 [File]\abc\cba\focus.txt
}
}
1.3.4 修改时间操作
- setLastModified() : 设置文件最后一次修改时间
- lastModified() : 得到文件最后一次修改时间
//setLastModified方法
import java.io.*;
public class TestSetLastModified{
public static void main(String[] args){
File x = new File("ToJayZhou.txt");
long now = System.currentTimeMillis();//获取当前时间
x.setLastModified(now + 10L*365*24*60*60*1000);//设置为10年后的时间,10L*365*24*60*60*1000为十年的毫秒数
}
}
//lastModified方法
import java.io.*;
import java.util.*;
import java.text.*;
public class TestLastModified{
public static void main(String[] args){
File x = new File("jay.jpg");
long time = x.lastModified();
3rd java.text.SimpleDateFormat
long时间戳 -> String字符串 使用format
String字符串 -> long时间戳 使用parse
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String ok = sdf.format(time);
System.out.println(ok);
2nd java.util.Calendar
日历类
Calendar cal = Calendar.getInstance();创建一个日历对象
cal.setTimeInMillis(time);将日历翻到time这一页
System.out.println(cal);
System.out.println(cal.get(1));年
System.out.println(cal.get(2) + 1);月
System.out.println(cal.get(5));号
System.out.println(cal.get(11));时
System.out.println(cal.get(12));分
System.out.println(cal.get(13));秒
System.out.println(cal.get(7)-1==0?7:cal.get(7)-1);周几,其中周日为int型的1,周六为int型的7
1st java.util.Date
使用时间类
Date theDate = new Date(time);————》时间戳
System.out.println(theDate.getYear() + 1900);直接getYear获得1900距离现在的年数,需要加上1900才是现在的年
System.out.println(theDate.getMonth() + 1);月份从0开始,需要加1
System.out.println(theDate.getDate());
System.out.println(theDate.getHours());
System.out.println(theDate.getMinutes());
System.out.println(theDate.getSeconds());
}
}
在Java中,Calendar
类中定义了一套用于表示日期和时间的值。在Calendar
类中,周一到周日分别用以下数字表示:
- 星期日:
Calendar.SUNDAY
(值为1) - 星期一:
Calendar.MONDAY
(值为2) - 星期二:
Calendar.TUESDAY
(值为3) - 星期三:
Calendar.WEDNESDAY
(值为4) - 星期四:
Calendar.THURSDAY
(值为5) - 星期五:
Calendar.FRIDAY
(值为6) - 星期六:
Calendar.SATURDAY
(值为7)
例如,您可以通过以下方式获取当前日期所对应的星期数:
Calendar cal = Calendar.getInstance();
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
请注意,返回的星期数是从1开始计数的,因此周一对应的值为2,周日对应的值为1。
在Java 8及更高版本中,还引入了新的日期和时间API(java.time
包)。对于星期的表示,该API使用以下枚举常量表示:
- 星期一:
DayOfWeek.MONDAY
- 星期二:
DayOfWeek.TUESDAY
- 星期三:
DayOfWeek.WEDNESDAY
- 星期四:
DayOfWeek.THURSDAY
- 星期五:
DayOfWeek.FRIDAY
- 星期六:
DayOfWeek.SATURDAY
- 星期日:
DayOfWeek.SUNDAY
这些枚举常量提供了更具可读性和类型安全性的方式来表示星期。
1.4 特等优先级方法(3种)
-
static listRoots( ) : 列出当前计算机的所有根目录(盘符)
File[] rts = File.listRoots();
-
String[ ] list( ) : 列出一个目录当中所有的文件名字
File dir = new File("."); String[] names = dir.list();
-
File[] listFiles() : 列出一个目录当中所有的文件对象
File dir = new File("."); File[] fs = dir.listFiles();
1.5 递归
//此方法用于查找所有盘中的图片共计多少个
import java.io.*;
public class TestSearch{
static int count;
public static void main(String[] args){
File[] rts = File.listRoots();
for(File r : rts){
kill(r);
}
System.out.println("kill方法总共被调用执行了:"+count+" 次");
}
//一个递归调用的核心方法 这个方法代表对付一个目录的完整过程
//方法的参数 代表正要对付的那个目录
public static void kill(File tar){
count++;
File[] fs = tar.listFiles();
if(fs == null) return;//判断是否有权限
for(File f : fs){
if(f.isDirectory()){
kill(f);
}else if(f.getName().toLowerCase().endsWith(".jpg")){
System.out.println(f);
}
}
}
}
1.6 过滤器、匿名函数、拉姆达表达式
/*
你知道c:\\吗
你知道c:\\下有windows目录吗
你知道windows目录下有system32目录吗
请用程序统计这个目录当中有多少个.dll文件
*/
//过滤器版本
import java.io.*;
public class TestFilter{
public static void main(String[] args){
File dir = new File("c:\\Windows\\system32");
File[] fs = dir.listFiles(new DllFilter());
System.out.println(fs.length);
}
}
class DllFilter implements FileFilter{
@Override
public boolean accept(File file){
return file.isFile() && file.getName().toLowerCase().endsWith(".dll");
}
}
//匿名函数版本
import java.io.*;
public class TestFilterAG{
public static void main(String[] args){
File dir = new File("c:\\Windows\\system32");
File[] fs = dir.listFiles(new FileFilter(){
@Override
public boolean accept(File f){
return f.isFile() && f.getName().toLowerCase().endsWith(".dll");
}
});
System.out.println(fs.length);
}
}
//拉姆达表达式版本
import java.io.*;
public class TestFilterAGAG{
public static void main(String[] args){
File dir = new File("c:\\Windows\\system32");
File[] fs = dir.listFiles((f) -> f.isFile() && f.getName().toLowerCase().endsWith(".dll"));
System.out.println(fs.length);
}
}
二、流
流的分类:
按照方向分: 输入流 输出流 *:参照物 = 当前程序
按照单位分: 字节流 字符流
按照功能分: 节点流 过滤流(包装流、处理流)在计算机科学中,节点流(Node Stream)和过滤流(Filter Stream)是两种不同类型的数据处理方式。
节点流是一种基本的数据流,它用于从源(如文件、网络连接)读取和写入到目标(如文件、网络连接)。节点流是以字节或字符为单位进行读写的,并且通常是同步的,即在读写操作完成之前,程序会阻塞等待。
过滤流是在节点流的基础上实现的一种数据处理方式。它们也用于从源读取数据和写入目标,但是通过添加数据过滤和转换的功能来扩展节点流的功能。过滤流可以对数据进行缓冲、压缩、加密、解密、序列化等操作,以便于更高级别的数据处理。过滤流是基于节点流的,因此它们是串行连接的,一个过滤流的输出可以作为另一个过滤流的输入。
两者的区别主要在于功能扩展和处理方式。节点流用于简单的数据读写,而过滤流则具有更高级别的功能,可以进行数据处理和转换。过滤流通常依赖于节点流,通过串行连接多个过滤流来完成复杂的数据处理任务。
2.1 字节流 (Byte streams)
2.1.1 InputStream 所有字节输入流统一的父类
- InputStream 中的方法
int read() :
int read(byte[] data) : *****
int read(byte[] data,int off,int len) :
2.1.2 OutputStream 所有字节输出流统一的父类
- OutPutStream 中的方法
write(int data)
write(byte[] data)
write(byte[] data,int off,int len) *****
2.2.3 重点注意事项
不能创建对象,是抽象类,但是却定义了每个字节输入流和字节输出流都要会的方法
2.2 文件流 (File streams)
2.2.1 FileInputStream 字节流 输入流 节点流
2.2.2 FileOutputStream 字节流 输出流 节点流
2.2.3 重点注意事项
-
作为节点流的它们 构造方法允许传入File对象/String文件路径
-
作为节点流的它们 只能连接文件 不能连接目录,如果链接目录,否则直接出现FileNotFoundException (拒绝访问)
-
FileInputStream 最常用的 read(byte[])
len = fis.read(data)
-
FileOutputStream 最常用的 write(byte[],int,int)
fos.write(data,0,len)
-
FileInputStream 以-1作为读取结束的标识
-
必须学会标准try catch处理异常和TWR
since jdk8.0
处理异常 -
FileOutputStream 是节点输出流,节点输出流创建对象的时候,如果连接的目标文件不存在,也会在创建流的那一刻自动创建出来 ,不需要手动创建,其实File类有个方createNewFile(),但是如果其连接的目录结构都不存在,将直接触发异常,系统找不到系统路径,所以File类,有个方法mkdirs(),一等优先级
File dir = new File("D:\\foto"); dir.mkdirs();
例如,FileOutputStream fos = new FileOutputStream ("Test\\a.jpg")如果没有Test这个目录,将会直接出现异常。
-
FileOutputStream 是节点输出流,节点输出流创建对象的时候,如果连接的目标文件已经存在,也会在创建流的那一刻被自动覆盖成新的空白文件,所以 它有极强的杀伤性,如果我们的需求是在原有内容之后追加连接新内容,可以构造方法传参 指定追加模式开启
new FileOutputStream("log.txt",true);
-
无论何时何地 请不要让节点输出流和节点输入流,同时连接同一个文件,否则怎样都是错
当节点输出流和节点输入流同时连接到同一个文件时,会出现以下情况: 1. 数据丢失:如果没有正确管理节点输出流和节点输入流之间的数据传输顺序,可能导致部分数据丢失或覆盖。这可能会导致文件中的部分内容被篡改或丢失。 2. 死锁:如果节点输出流和节点输入流都试图同时访问同一个文件的同一部分,可能会发生死锁。死锁是指两个或多个进程在互相等待对方释放资源,导致程序无法继续执行的情况。 3. 冲突和竞争条件:当节点输出流和节点输入流同时写入同一个文件时,可能会引发冲突和竞争条件。竞争条件是指多个进程试图同时访问或修改共享资源而导致的问题,可能会破坏文件的完整性或引起意外行为。 为了避免上述问题,应该确保合适地同步和管理节点输出流和节点输入流之间的数据传输,以避免并发访问同一文件的冲突。可以使用互斥锁、信号量或其他同步机制来解决这些问题。此外,编程时应注意正确处理异常和错误情况,以确保文件操作的稳定性和可靠性。
2.2.4 创建FileInputStream、FileOutputStream以及实际操作
2.2.4.1 创建FileInputStream
这段代码中的 while
循环用于从文件 “abc.txt” 中读取数据。循环条件是 fis.read(data)
不等于 -1,这意味着当文件中还有数据可读时,循环将继续执行。
在每次循环迭代中,fis.read(data)
方法会尝试从文件中读取最多 data.length
个字节的数据,并将其存储在 data
数组中。该方法返回实际读取的字节数,该值存储在变量 len
中。
接下来,循环中的 for
循环会遍历 data
数组中的前 len
个元素,并将它们转换为字符后打印出来。
当文件中没有更多数据可读时,fis.read(data)
方法将返回 -1,此时 while
循环将终止。最后,调用 fis.close()
方法关闭文件输入流。
import java.io.*;
public class TestFileInputStream{
public static void main(String[] args)throws Exception{
FileInputStream fis = new FileInputStream("abc.txt");
byte[] data = new byte[3];
int len;
while((len = fis.read(data))!=-1){
for(int i = 0;i<len;i++){
System.out.print((char)data[i]);
}
}
fis.close();
}
}
2.2.4.2 文件的复制
这段代码中的 while
循环用于从文件 “1.mp3” 中读取数据并将其写入文件 “3.mp3”。循环条件是 fis.read(data)
不等于 -1,这意味着当文件 “1.mp3” 中还有数据可读时,循环将继续执行。
在每次循环迭代中,fis.read(data)
方法会尝试从文件 “1.mp3” 中读取最多 data.length
个字节的数据,并将其存储在 data
数组中。该方法返回实际读取的字节数,该值存储在变量 len
中。
接下来,调用 fos.write(data,0,len)
方法将 data
数组中的前 len
个字节写入文件 “3.mp3”。
当文件 “1.mp3” 中没有更多数据可读时,fis.read(data)
方法将返回 -1,此时 while
循环将终止。最后,调用 fos.close()
和 fis.close()
方法分别关闭文件输出流和文件输入流。
fos.write(data,0,len)
中的第二个参数 0
表示从 data
数组的第一个元素(即下标为 0 的元素)开始写入数据。第三个参数 len
表示要写入的字节数,即从 data
数组中写入前 len
个字节。
import java.io.*;
public class TestFileCopy{
public static void main(String[] args)throws Exception{
FileInputStream fis = new FileInputStream("1.mp3");
FileOutputStream fos = new FileOutputStream("3.mp3");
byte[] data = new byte[5<<20];
int len;
while((len = fis.read(data))!=-1){
fos.write(data,0,len);
}
fos.close();
fis.close();
}
}
2.2.4.3 TWR与连环try
- TWR,将对文件流的操作放到try的()内部,当try{ }结束后,自动进行close操作
//TWR => try-with-resources => 带有资源控制的try catch since JDK7.0
import java.io.*;
public class TestFileCopyWithTWR{
public static void main(String[] args){
try(FileInputStream fis = new FileInputStream("1.mp3");
FileOutputStream fos = new FileOutputStream("3.mp3")){
byte[] data = new byte[5<<20];
int len;
while((len = fis.read(data))!=-1){
fos.write(data,0,len);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
- 连环try-catch,要把资源的关闭放到finally中
//TestExceptionPlus3 (连环try) + TestExceptionPlus4 (定义拿到try{}前面去)
import java.io.*;
public class TestFileCopyWithTryCatch{
public static void main(String[] args){
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("1.mp3");
fos = new FileOutputStream("3.mp3");
byte[] data = new byte[5<<20];
int len;
while((len = fis.read(data))!=-1){
fos.write(data,0,len);
}
}catch(Exception e){
e.printStackTrace();
}finally{
try{
fos.close();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
fis.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
}
2.3 缓冲流 (Buffered streams)
2.3.1 BufferedInputStream 过滤流 给原本的节点流添加缓冲空间从而提高读取效率
2.3.2 BufferedOutputStream 过滤流 给原本的节点流添加缓冲空间从而提高写出效率
2.3.3 重点注意事项
-
作为过滤流的它们 给原本的流添加缓冲空间,从而提高每次读写的吞吐量 进而提高效率
-
BufferedInputStream
和BufferedOutputStream
都是通过使用缓冲区来提高读写效率的。对于
BufferedInputStream
,它会一次性从底层输入流中读取多个字节的数据并存储在内部缓冲区中。当调用read()
方法时,它会直接从缓冲区中获取数据,而不是每次都从底层输入流中读取。这样可以减少对底层输入流的访问次数,从而提高读取效率。对于
BufferedOutputStream
,它会将写入操作暂存到内部缓冲区中,而不是立即将数据写入底层输出流。当缓冲区被填满时,它会一次性将缓冲区中的数据写入底层输出流。这样可以减少对底层输出流的访问次数,从而提高写入效率。总之,
BufferedInputStream
和BufferedOutputStream
都是通过减少对底层输入/输出流的访问次数来提高读写效率的。
-
-
它们都是过滤流 不能直接连接文件 只能连接其它的流
-
它们构造方法第二个参数 都可以指定缓冲空间的大小,
默认8192个字节 8k = 1024*8
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.mp3");,5<<20); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.mp3");,5<<20);
-
BufferedInputStream 最常用的 read()
-
BufferedOutputStream 最常用的 write(int data)
//注意区分与FileInputStream、FileOutStrem的while循环 //buffered int data; while((data = bufferedInputStream.read())!=-1){ bufferedOutputStream.write(data); } //File byte[] data = new byte[5 << 20]; int len; while((len = fileInputStream.read(data))!=-1){ fileOutputStream.write(data,0,len); }
-
BufferedInputStream 同样以-1作为读取结束的标识
-
BufferedOutputStream 是带缓冲区的输出流,使用带缓冲的输出流 必须注意及时清空缓冲,以防止数据滞留缓冲而导致丢失
缓冲区什么条件下会清空:
1.满了自动清空 无需操作
2.关闭流的操作会触发清空缓冲 close();
3.主动清空释放缓冲区 flush();
-
这两个流 结合TWR的时候 需要一步到位 否则影响效率
import java.io.*; public class TestBufferedStreamWithTWR{ public static void main(String[] args){ try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.mp3"),5<<20); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.mp3"),5<<20);){ int data; while((data = bis.read())!=-1){ bos.write(data); } }catch(Exception e){ e.printStackTrace(); } } }
2.4 数据流(Data streams)
2.4.1 DataInputStream 过滤流 给原本的节点流添加读取基本数据类型的功能
2.4.2 DataOutputStream 过滤流 给原本的节点流添加写出基本数据类型的功能
2.4.3 重点注意事项
-
作为过滤流的它们,给原本的流添加读写基本数据类型的功能
-
作为过滤流的它们 只能连接其它的流 不能直接连接文件
-
boolean char byte short int long float double
-
DataInputStream 提供的核心方法 readXxxx(); 有返回值
-
DataOutputStream 提供的核心方法 writeXxxx(); 要参数
-
DataInputStream 不能再以-1作为读取结束的标识(会产生歧义,可能本身读到Int型的-1),如果已经到达文件结尾还继续尝试读取,将直接触发EOFException => End Of File
//可以通过捕获EOFException 来判断是否到达文件结尾 try { // 读取数据 int data = dataInputStream.readInt(); } catch (EOFException e) { // 到达文件末尾 e.printStackTrace(); }
throws Exception版本
import java.io.*;
public class TestDataStream{
public static void main(String[] args)throws Exception{
int level = 658;
//存档
FileOutputStream fos = new FileOutputStream("save.data");
DataOutputStream dos = new DataOutputStream(fos);
dos.writeInt(level);//把要存取的数据传入
dos.close();
//读档
FileInputStream fis = new FileInputStream("save.data");
DataInputStream dis = new DataInputStream(fis);
int result = dis.readInt();//将信息取出
dis.close();
System.out.println(result);
}
}
try-catch版本
import java.io.*;
public class HelloWorld{
public static void main(String[] args){
int cs = 1;
//读档环节
File file = new File("cs.data");
if(file.exists()){
try(DataInputStream dis = new DataInputStream(new FileInputStream(file))){
cs = dis.readInt();
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println("HelloWorld:"+cs);
cs++;
//开始存档
try(DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))){
dos.writeInt(cs);
}catch(Exception e){
e.printStackTrace();
}
}
}
2.5 对象流 (Object streams)
2.5.1 ObjectInputStream 过滤流 给原本的节点流添加读取对象的功能
2.5.2 ObjectOutputStream 过滤流 给原本的节点流添加写出对象的功能
2.5.3 重点注意事项
-
作为过滤流的它们,是为了给原本的流读写对象的功能,它们都是过滤流 不能连接文件 只能连接其它的流
-
ObjectInputStream readObject()
有返回值,接受返回的对象
-
ObjectOutputStream writeObject()
要参数,传入创建的对象
-
ObjectInputStream 同样不能以-1作为读取结束的标识,而是一旦到达文件结尾还继续尝试读取,将触发EOFException = End Of File
-
想要持久化 必须先要序列化,想要被写出到磁盘上的对象,它的类必须要实现
Serializable
接口,如果其中还有其它引用类型的属性,就连这些属性的类型,也要实现序列化接口,如果某些属性无关紧要 不需要参与持久化保存,则可以使用transient修饰符修饰transient
=> 短暂的 不参与持久化的Serializable中的异常
InvalidClassException:这个异常会在反序列化过程中抛出,当类的序列化版本与反序列化时的版本不匹配时,会导致无法正确重建对象。解决方法是确保序列化之前和之后的类是兼容的,可以通过手动指定serialVersionUID来确保版本一致。
序列化是一种将对象的状态转换为字节流的机制。反序列化是相反的过程,其中字节流用于在内存中重新创建实际的 Java 对象。这种机制用于持久化对象²⁴。 Java 指定了一种默认的序列化对象的方式。Java 类可以覆盖这种默认序列化并定义自己的序列化对象的方式¹。 根据 Java 对象序列化规范,我们可以使用 `ObjectOutputStream` 类的 `writeObject()` 方法来序列化对象。另一方面,我们可以使用 `ObjectInputStream` 类的 `readObject()` 方法来执行反序列化¹。 简而言之,序列化是将 Java 对象转换为静态字节流,然后我们可以将其保存到数据库或通过网络传输。反序列化是相反的过程,它可以从字节流中读取数据并在内存中重新创建 Java 对象。
序列化是指将对象转换为字节流的过程,以便在网络传输或存储中进行使用。 整个序列化过程可以分为以下几个步骤: 1. 对象准备:需要进行序列化的对象必须实现序列化接口,如Java中的`Serializable`接口。该接口标记对象可以被序列化,并且可以自动将对象的状态保存到字节流中。 2. 对象转换:在进行序列化之前,需要创建一个输出流,将对象写入其中。这个输出流可以是文件、网络流或内存流。比如,Java中可以使用`ObjectOutputStream`来将对象写入字节流中。 3. 对象分解:在对象被序列化为字节流之后,对象中的数据将被分解为一系列的字节表示。这个过程包括将对象的属性、方法和其他信息转换为字节形式。 4. 字节流写入:将分解后的字节流写入到输出流中。字节流可以被传输到其他计算机或存储在磁盘上,以便以后进行反序列化操作。 序列化的原理可以简单概括为以下几点: 1. 序列化对象时,会将对象的数据进行分解,并将分解后的数据以字节流的形式保存。这包括对对象的属性进行编码和保存。 2. 对象的属性值会被转换为字节表示形式,可以是基本数据类型如整数和浮点数的字节表示,也可以是其他对象的字节表示。 3. 对象的方法、类信息和其他元数据也会被序列化,并一同保存到字节流中。这样在反序列化时可以重新构建对象的结构和行为。 4. 序列化过程中还需要处理一些特殊情况,比如循环引用问题,即一个对象引用了另一个对象,而后者又引用回前者。序列化时需要处理该引用关系,以保证对象在反序列化后能正确重建。 总体来说,序列化是一种将对象转换为字节流的机制,它使得对象可以在不同的环境中进行传输和存储。反序列化则是将字节流转换回对象的过程,使得对象的状态和行为可以恢复。序列化的过程涉及到将对象分解为字节表示,保存到字节流中,并在需要时进行反序列化来还原对象。
-
如果要持久化的是一个集合对象,则集合当中的元素的类 也必须要实现序列化接口,例如
List<Student> list = new ArrayList<>()
,其中的Student要实现序列化接口 -
如果要持久化的是一个使用了比较器的TreeSet或者TreeMap,就连比较器的类也要实现序列化接口,因为比较器是TreeSet或者TreeMap的一个属性
对象流的基础实现
import java.io.*;
import java.util.*;
public class TestObjectStream{
public static void main(String[] args)throws Exception{
Date today = new Date();//日期
//存档
FileOutputStream fos = new FileOutputStream("月光宝盒.data");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(today);//将参数传进去
oos.close();
//读档
FileInputStream fis = new FileInputStream("月光宝盒.data");
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();//接收返回值
ois.close();
System.out.println(obj);
}
}
序列化+transient修饰符
import java.io.*;
public class TestObjectStream2{
public static void main(String[] args)throws Exception{
//创建对象
Teacher tea = new Teacher("JayZhou",36);
//存档
FileOutputStream fos = new FileOutputStream("电冰箱.data");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(tea);
oos.close();
//读档
FileInputStream fis = new FileInputStream("电冰箱.data");
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
System.out.println(obj);
}
}
class Computer{
String logo;
public Computer(String logo){
this.logo = logo;
}
}
class Teacher implements Serializable{//序列化Teacher类
String name;
int age;
//短暂的 转瞬即逝的 : 不参与持久化的...
transient Computer pc;
public Teacher(String name,int age){
this.name = name;
this.age = age;
pc = new Computer("Lenovo");
}
@Override
public String toString(){
return name + " : " + age;
}
}
2.6 字符流(character stream)
在学完字节流之后 我们为什么要学字符流?
字节流适合原封不动的读写复制文件,但是没法解读文件当中的文字(中国字)得用字符流,而字符流过于智能它只能读取文本文件,不能用于复制
2.6.1 Reader 和 Writer
Reader 所有字符输入流统一的父类 抽象类
int read()
int read(char[] data)
int read(char[] data,int off,int len)
Writer 所有字符输出流统一的父类 抽象类
write(int)
write(char[] data)
write(char[] data,int off,int len)
2.6.2 FileReader 和 FileWirter
-
FileReader 输入流 字符流 节点流
-
FileWriter 输出流 字符流 节点流
-
作为节点流的它们 构造方法允许传入File对象/String路径
- 例如
FileReader fr = new FileReader("abc.txt");
- 例如
-
虽然贵为节点流 但是只能连接文件 不能连接目录 否则直接触发异常 FileNotFoundException(拒绝访问)
-
FileReader 最常用的read(char[])
-
FileWriter 最常用的write(char[],int,int)
-
FileReader 以-1作为读取结束的标识
-
FileWriter 是节点输出流 节点输出流创建对象的时候,其连接的文件 如果不存在 也会被自动创建出来,但是如果其连接的目录结构都不存在 将直接异常
-
FileWriter 是节点输出流 它创建对象的时候,如果连接的文件已经存在 也会在创建流的那一刻,被新的空白文件直接覆盖 ,如果想要在原有内容之后追加新内容,则构造方法传参 指定追加模式开启
new FileWriter("abc.txt",true);
2.6.3 BufferedReader 和 BufferedWriter
-
BufferedReader 输入流 字符流 过滤流
-
BufferedWriter 输出流 字符流 过滤流
-
作为过滤流的它们 给原本的流添加变长的缓冲空间,从而实现以行为单位的读写
-
它们都是过滤流 不能直接连接文件 只能连接其它的流
-
BufferedReader 不再以-1作为读取结束的标识了,以null作为读取结束的标识
FileReader fr = new FileReader("focus.txt"); BufferedReader br = new BufferedReader(fr); String str; while((str = br.readLine())!=null){ System.out.println(str); } br.close();
-
BufferedReader String readLine(),常用
-
BufferedWriter write(String) + newLine(),用printWriter替代
-
BufferedWriter 是个带缓冲区的输出流,使用它务必注意及时清空缓冲
缓冲区什么条件下会清空:
1.满了自动清空 无需操作
2.关闭流的操作会触发清空缓冲 close();
3.主动清空释放缓冲区 flush();
2.6.4 PrintWriter
PrintWriter 比 BufferedWriter 好在哪?
1.PrintWriter 既可以当做节点流
又可以当做过滤流
,构造方法可以传入String路径/File对象/流
2.PrintWriter 既可以连接字节流
又可以连接字符流
,构造方法允许传入OutputStream / Writer
3.当做节点流使用的时候 构造方法第二个参数,可以指定字符编码new PrintWriter("a.txt","utf-8");
4.当做过滤流
使用的时候 构造方法第二个参数,可以指定自动清空缓冲
,自动清空缓冲是指 见到换行 就发车!new PrintWriter(new FileWriter("a.txt"),true);
5.它的一行println() = write() + newLine(),代码量大幅度减小
6.我们还对它的孪生兄弟 特别熟悉System.out.println() System.out => PrintStream 7.是带缓冲区的输出流 使用它们务必注意及时清空缓冲,以防止数据滞留缓冲空间 导致丢失
综上所述 今后我们以行为单位写出文本文件的时候,根本不会选择BufferedWriter,而会选择 更强大的PrintWriter
2.7 桥转换器 InputStreamReader
桥转换器 InputStreamReader
是 Java 编程语言中的一个类。它负责将字节流(InputStream
)转换为字符流(Reader
)。
InputStreamReader
的作用是将输入的字节流转换为字符流,便于字符流的处理和读取。它提供了多个构造函数,可以指定不同的字符集来进行字节到字符的转换。
下面是一个示例代码,展示了如何使用 InputStreamReader
将字节流转换为字符流:
// 创建输入字节流
InputStream inputStream = new FileInputStream("input.txt");
// 创建字符流桥转换器
InputStreamReader reader = new InputStreamReader(inputStream, "UTF-8");
// 读取字符流
int data;
while ((data = reader.read()) != -1) {
char character = (char) data;
System.out.print(character);
}
// 关闭字符流
reader.close();
InputStreamReader是Java IO库中的一个类,它的作用是将字节流(InputStream)转换为字符流(Reader)
,即将字节转换为字符。
它提供了一种将字节数据流转换为字符数据流的桥梁
,可以读取字节流并将其解码为字符数据。它可以接受多种不同的字节流作为输入,例如FileInputStream、ByteArrayInputStream等,将这些字节流读取到指定的字符编码中。
InputStreamReader可以指定与特定字符编码相关的解码方式,以确保正确地将字节转换为字符。
可以通过构造函数的参数指定字符集编码(例如UTF-8、GBK等),或使用默认的平台字符编码。
使用InputStreamReader时,可以通过read()、read(char[] cbuf)等方法读取字符数据,并以特定字符编码进行解码。也可以使用BufferedReader来包装InputStreamReader,提供缓冲功能以提升读取效率。
总之,InputStreamReader的主要作用是将字节流转换为字符流,方便以字符方式读取和处理数据。
2.8 网络流
2.8.1 URL类
2.8.1.1 导包
import java.net.*
2.8.1.2 创建
URL url = new URl("http://old.etoak.com/assets/images/zkcs.png");
URLConnection uc = url.openConnection();
InputStream is = uc.getInputStream();
代码解释:
URL url = new URL("http://old.etoak.com/assets/images/zkcs.png");
:创建一个URL
对象,并传入一个指向图像资源的URL字符串。在这个例子中,它指向名为zkcs.png
的图像文件的URL。URLConnection uc = url.openConnection();
:通过调用URL
对象的openConnection()
方法,创建一个URLConnection
对象。URLConnection
代表与指定URL的连接,并提供了与该URL的通信功能。InputStream is = uc.getInputStream();
:通过调用URLConnection
对象的getInputStream()
方法,获取与URL连接的输入流。
2.8.1.3 下载文件
下载文件的原理就是文件的复制
URL url = new URl("http://old.etoak.com/assets/images/zkcs.png");
URLConnection uc = url.openConnection();
InputStream is = uc.getInputStream();
FileOutputStream fos = new FileOutputStream();
byte data = new byte[1024];
int len;
while((len = is.read(data))!=-1){
fos.write(data,0,len);
}
fos.close();
is.close();
2.9 客户端与服务端之间进行通信(C/S架构)
2.9.1 创建服务器的核心代码
========================================主方法===========================================
//创建线程池
ExecutorService es = Executors.newCachedThreadPool();//0个活跃线程,来一个任务创建一个线程
//创建server对象
ServerSocket server = new ServerSocket(6666);//指定端口号6666
//设置循环条件
boolean isRunning = true;
while(isRunning){
//打开连接,获得连接管道
Socket skt = server.accept();
//将连接管道skt传入线程,创建线程
ServerThread st = new ServerThread(skt);
//将线程放入线程池
es.submit(st);
}
//关闭连接
server.close();
//关闭线程池
es.shutdown();
========================================创建线程类=========================================
class ServerThread extends Thread {
//创建Socket对象
Socket skt;
//构造方法传入参数
public ServerThread(Socket skt){
this.skt = skt;
}
@Override
public void run(){
try{
//创建InputStream,从客户端读取用户输入的数据
InputStream is = skt.getInputStream();
//按照指定格式读取用户输入的数据
InputStreamReader isr = new InputStreamReader(is,"utf-8");
//缓冲流加持读取一行
BufferedReader br = new BufferedReader(isr);
String username = br.readLine();
String password = br.readLine();
//校验数据
int flag = 1;
if("wangfan".equals(username)){
if("123456".equals(password)){
//登陆成功
flag = 0;
}else{
//密码错误
flag = 2;
}
}else{
//用户名错误
flag = 1;
}
//发送数据,将flag发送给用户
OutputStream os = skt.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(flag);
//关闭资源
dos.close();
br.close();
skt.close();
System.out.println(skt.getInetAddress() + ":" + username + ":" + password + "->" + flag);
}catch(Exception e){
e.printStackTrace();
}
}
}
2.9.2 创建客户端核心代码
//创建管道,用户与服务器通信
Socket skt = new Socket("127.0.0.1",6666);
//发送数据,指定编码发送
OutputStream os = skt.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os,"utf-8");
PrintWriter pw = new PrintWriter(osw,true);
pw.println("wasngfan");
pw.println("7");
//接受服务器返回的数据
InputStream is = skt.getInputStream();
DataInputStream dis = new DataInputStream(is);
int x = dis.readInt();
System.out.println(x == 0 ? "登陆成功" : (x==1 ? "用户名错误" : "密码错误") );