文章目录
- 2. IO流 - ( input/output流 )
- 3. NIO流 - ( New input/output流 )
- 4 I/O性能
2. IO流 - ( input/output流 )
2.1 分类
本图来自于视频截图
2.2 具体的流类
2.2.1 字节输入流、输出流( InputStream、OutputStream ) – 永远是根基
不管怎么优化:底层上字节输入、输出流依然还是一个一个字节上进行读、写操作
FileInputStream、FIleOutputStream
针对文件的字节输入、输出流
字节输出流,没有自带的输出缓存
简单使用
public static void copyFile(String sourceFilePath, String targetFilePath) {
InputStream fis = null;
OutputStream fos = null;
try {
// 1. 构建字节输入、输出流对象
fis = new FileInputStream( sourceFilePath );
fos = new FileOutputStream( targetFilePath );
// 2. 用来获取每次从字节流读取的字节 -- 自动转为int型
int readChar = -1;
/*
// 2. 用来获取每次从字节流读取1024个字节,len标识实际读取到该数组的字节长度
char[] buffer = new buffer[1024];
int len = -1;
*/
// 3. 将从字节输入流中读取的数据一个一个字节写入到目标文件上 -- 性能差
while((readChar = fis.read()) != -1) {
fos.write(readChar);
}
/*
// 3. 将从字节输入流中读取的数据以len数组长度字节写入到目标文件上 -- 性能好
while((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0 , len);
}
*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭字节输入、输出流防止内存泄漏
if(fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//应用实例 - 运行下列的代码:
copyFile("C:/Users/lrc/Desktop/字符流.png", "C:/Users/lrc/Desktop/copyImg.png")
2.2.2 字符输入流、输出流( InputReader、OutputWriter )
内部实现依然是字节流
FileReader、FileWriter
1. 针对文件的字符输入、输出流
2. 文字字符操作流自动缓存 - Write只是写在一个输出缓存中,还没放入文件、硬盘上,需要缓存满、flush()、或者 close() 才会从内存输出到持久化文件上
3. 不管怎么优化:底层上字节输入、输出流实现依然还是按字节上进行读、写操作
4. 默认自带缓存是8k
简单使用 - 只能是文本文件
public static void copyFile(String sourcePath, String targetPath) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader(sourcePath);
fw = new FileWriter(targetPath);
char[] buffer = new char[1024];
int len = -1;
while((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//应用实例 - 运行下列的代码:
copyFile("C:/Users/lrc/Desktop/项目测试SQL.sql", "C:/Users/lrc/Desktop/a.txt");
2.2.3 转换流
解决到时别人给的流是不好处理尴尬情况,需要装换为其他的流进行处理
OutputStreamWriter,InputStreamReader
1. OutputStreamWriter: 字节流转为字符流的方式进行处理
2. InputStreamWriter: 字节流转为字符流
简单示例 - 字节流转字符流进行复制文件
public static void newFile(InputStream is, String targetPath) {
Reader reader = null;
FileWriter fos = null;
try {
reader = new InputStreamReader(is);
fos = new FileWriter(targetPath);
char[] buffer = new char[1024];
int len = -1;
while((len = reader.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//应用实例 - 运行下列的代码:
newFile("C:/Users/lrc/Desktop/项目测试SQL.sql", "C:/Users/lrc/Desktop/a.txt");
简单示例 - 输入、输出流的处理
public static void addContent(OutputStream os, InputStream is) {
OutputStreamWriter osw = null;
InputStreamReader isr = null;
try {
osw = new OutputStreamWriter(os, "utf-8");
isr = new InputStreamReader(is);
char[] buffer = new char[1024];
int len = -1;
while ((len = isr.read(buffer)) != -1) {
osw.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (osw != null) {
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//应用实例 - 运行下列的代码: -- 运行两次
InputStream is = new FileInputStream("C:\\Users\\lrc\\Desktop\\项目测试SQL.sql");
OutputStream os = new FileOutputStream("C:\\Users\\lrc\\Desktop\\a.txt", true);
addContent(os,is);
2.2.4 缓冲流
1. 解决痛点:硬盘、内存的读写速率相差非常的大,频繁的从硬盘进行读写效率低、性能差
2. 可将缓冲流类比成购物车,而不是一个一个用手拿
BufferedInputStream,BufferedOutputStream
1. BufferedInputStream: 为另一个字节流添加内部缓存区数组,用于缓冲数据
2. BufferedOutputStream 将字节写入底层输出流中,而不是每个字节写入调用底层系统3. 字节流没有自带的缓冲,可通过此留提升读写效率
4. 默认是缓冲数组是8KB,一旦数组满,自动刷新
简单示例:复制文件 - 字节缓冲流
public static void newFile2(InputStream is, OutputStream os) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
byte[] buffer = new byte[1024];
int len = -1;
while((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭缓冲流,会自动的将字节流刷新读入、写出
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//应用实例 - 运行下列的代码:
InputStream is = new FileInputStream("C:\\Users\\lrc\\Desktop\\字节流.png");
OutputStream os = new FileOutputStream("C:\\Users\\lrc\\Desktop\\a.png", true);
newFile2(is, oss);
BufferedReader,BufferedWriter
1. 缓冲各个字符,从而实现高效的读、写字符、数组、行
3. 字符流自带缓冲,通过该缓冲流可让其效率更加的高 - 并且可以指定缓存大小
简单示例:复制文本文件 - 字符缓冲流
public static void newFile3(Reader r, Writer w) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(r);
bw = new BufferedWriter(w);
char[] buffer = new char[1024];
int len = -1;
while((len = br.read(buffer)) != -1) {
bw.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.2.5 打印流
增强输出流的功能
PrintStream( 字节 ),PrintWriter( 字符 )
PrintStream - 往某个文件增添内容
public static void bytePrint(String targetPath, String addContent) {
PrintStream ps = null;
try {
OutputStream os = new FileOutputStream(targetPath, true);
BufferedOutputStream bs = new BufferedOutputStream(os);
ps = new PrintStream(bs, true);
ps.println(addContent);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
PrintWriter – 往某个文件增添内容
public static void charPrint(String targetPath, String addContent) {
PrintWriter pw = null;
try {
// 节点流
Writer w = new FileWriter(targetPath, true);
// 处理流:硬盘写入的效率更加好
BufferedWriter bw = new BufferedWriter(w);
// 处理流:增加多种打印的内容方法
pw = new PrintWriter(bw, true);
pw.println(addContent);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (pw != null) {
pw.close();
}
}
}
2.2.6 序列化、反序列化
1. 需要序列化的类必须实现 Serializable接口
2. 序列化: 把对象属性值存入硬盘上、反序列化: 文件上的内容还原成一个对象
3. 如果一个类有transient修饰的成员属性,则该属性的属性值不会被序列化文件上
ObjectInputStream( 反序列化 ),ObjectOutputStream( 序列化 )
一个关于Emp类的JavaBean,用来下面序列化、反序列的测试
public class Emp implements Serializable{
Integer empno;
String ename;
transient Integer year;
public Integer getEmpno() {
return empno;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
@Override
public String toString() {
return "Emp [empno=" + empno + ", ename=" + ename + ", year=" + year + "]";
}
}
简单测试 - 序列化单个对象
public static void writeObject(String targetPath, Object targetObject) {
ObjectOutputStream oos = null ;
try {
OutputStream os = new FileOutputStream(targetPath);
oos = new ObjectOutputStream(os);
oos.writeObject(targetObject);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if( oos!=null ) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 测试代码
Emp emp = new Emp();
emp.setEmpno(1111);
emp.setEname("lrc");
emp.setYear(21);
writeObject("C:\\Users\\lrc\\Desktop\\emp.txt", emp);
简单测试 - 反序列化对象
public static Object readObject(String targetPath) {
ObjectInputStream ois = null;
try {
InputStream is = new FileInputStream(targetPath);
ois = new ObjectInputStream(is);
Object object = ois.readObject();
return object;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if( ois!=null ) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
// 测试代码
// 将序列化的文件的反序列化为一个对象
Object o = readObject("C:\\Users\\lrc\\Desktop\\emp.txt");
System.out.println( o );
简单测试 - 一个文件上存储多个序列化对象
一个序列化文件只能存储一个对象,故想要存储多个对象,可将其存在数组、容器中,然后序列化这个数组或者容器对象
Emp emp1 = new Emp();
emp1.setEmpno(1111);
emp1.setEname("lrc");
emp1.setYear(21);
Emp emp2 = new Emp();
emp2.setEmpno(1112);
emp2.setEname("lrd");
emp2.setYear(22);
Emp[] emps = {emp1, emp2};
// 序列化数组对象成为一个文件
writeObject("C:\\Users\\lrc\\Desktop\\emp.txt", emps);
// 反序列化数组文件
Object o = readObject("C:/Users/lrc/Desktop/emp.txt");
Object[] os = (Object[])o;
System.out.println(Arrays.toString(os));
运行结果
2.2.6 字节字符数组流 – 与文件无关
1. 拥有内部缓冲区 – 只针对内存操作
2. 关闭此流无效,此对象流的方法仍然有效
3. 可以不用关闭此流,本质内部就是个数组
ByteArrayInputStream,ByteArrayOutputStream
跟字符数组流差不多
CharArrayInputStream,CharArrayOutputStream
简单示例 - 用上述两者字符数组流进行筛选字符
public static char[] getLetter(String str) {
try {
CharArrayReader car = new CharArrayReader(str.toCharArray());
CharArrayWriter caw = new CharArrayWriter();
int currentChar = -1;
while ((currentChar = car.read()) != -1) {
System.out.println((char)currentChar);
if ((65 <= currentChar && currentChar <= 90) || (97 <= currentChar && currentChar <= 122)) {
caw.write(currentChar);
}
}
return caw.toCharArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//测试代码
char[] a = getLetter("凡事都疯了似的432fsdafsdfSFDDSfsdf凡事都浪费");
System.out.println(new String(a));
2.2.6 数据流
DataInputStream、DataOutputStream
1. DataInputStream:使用底层输入流读取操纵Java基本数据类型,由数据输出流写入,数据输入流读取数据 - 输入字节大小完全根据Java决定
2. DataOutputStream:应用程序将基本数据类型写入输出流中,使用输入流将数据读入
3. 线程安全可选
4. 写读顺序一致,要不然读取会乱码
简单示例 - 写入基本数据类型数据到文件
public static void writeData(String sourcePath, String content) {
DataOutputStream dos = null;
try {
OutputStream os = new FileOutputStream(sourcePath);
dos = new DataOutputStream(os);
dos.writeUTF(content);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if( dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//测试代码
writeData("C:\\Users\\lrc\\Desktop\\emp.txt", "fsdfsdafsdr放松放松地方1111");
简单示例 - 从文件中读取由DataOutStream写入的数据
public static void readData(String sourcePath) {
try {
InputStream is = new FileInputStream(sourcePath);
DataInputStream dis = new DataInputStream(is);
String readStr = dis.readUTF();
System.out.println(readStr);
dis.close();
}catch(Exception e) {
e.printStackTrace();
}
}
//测试代码
readData( "C:\\Users\\lrc\\Desktop\\emp.txt" );
2.2.6 其他流
StringReader,StringWriter - 字符串流( 字符流 )
可将字符串转成字符流
作用:到时可将字符串数据转成字符流进行解析数据
简单示例 - 计算字符串的单词个数
public static Integer getWordCount(String target) {
StringReader sr = new StringReader(target);
StreamTokenizer st = new StreamTokenizer(sr);
int count = 0;
while(st.ttype != StreamTokenizer.TT_EOF) {
try {
if( st.nextToken() == StreamTokenizer.TT_WORD ) {
count++;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return count;
}
// 测试代码
int count = getWordCount("23 happy new 465 year 分公司的但是 1245fds");
System.out.println( count );
运行结果
PipedInputStream,PipedOutputStream - 管道流(字节流)
特点:
- 1. 管道输入流 提供数据给 管道输出流
- 2. 管道输入流自带缓冲区
- 3. 通常:1个线程 管理 管道输入流,其他线程 管理 管道输出流
- 4. 管道输入流的线程不存在,则管道已被破坏
作用: 线程之间的通讯
1. PipeOutput线程
class PipeOutput implements Runnable {
String sourcePath;
PipedOutputStream pos;
PipeOutput(PipedOutputStream pos, String sourcePath) {
this.pos = pos;
this.sourcePath = sourcePath;
}
@Override
public void run() {
InputStream is = null;
try {
is = new FileInputStream(sourcePath);
byte[] buffer = new byte[2048];
int len = -1;
// 将外部文件的内容写入到内存上
while ((len = is.read(buffer)) != -1) {
pos.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (pos != null) {
try {
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2, PipeInput线程
class PipeInput implements Runnable {
PipedInputStream pis;
String receive;
PipeInput(PipedInputStream pis) {
this.pis = pis;
}
public String getReceive() {
return receive;
}
@Override
public void run() {
StringBuilder sb = new StringBuilder();
try {
byte[] buffer = new byte[2048];
int len = -1;
// 将输出管道流写进内存的内容进行读取,并把读取的内容用 StringBuilder保存,最后存到receiver
while ((len = pis.read(buffer)) != -1) {
String a = new String(buffer, 0, len);
sb.append(a);
}
receive = sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. 测试上述两个管道流线程 - 在另一个类中定义的
@Test
public void test1() throws IOException, InterruptedException {
// 1. 创建输入、输出管道流
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
// 2. 将两个管道进行连接
pis.connect(pos);
// 3. 输入、输出管道流在各自的线程上进行运行
PipeInput pi = new PipeInput(pis);
PipeOutput po = new PipeOutput(pos, "C:\\Users\\lrc\\Desktop\\项目测试SQL.sql");
Thread tRead = new Thread(pi);
Thread tWrite = new Thread(po);
// 4. 启动线程,当前有三个线程进行运行, tRead、tWrite、以及调用test1的线程
tRead.start();
tWrite.start();
// 5. 休眠test1函数的线程,因为 tRead、tWrite线程会阻塞,导致提前调用test1的线程,从而致使下面的receive为空
Thread.currentThread().sleep(1000);
// 6. 获取tRend线程的 receive成员变量的值,打印输出到控制台
String receive = pi.getReceive();
if (receive != null) {
System.out.println(receive.length());
System.out.println(receive);
} else {
System.out.println("没有接收到东西");
}
}
运行结果
RandomAccessFile – 以字节流的方式对文件进行读写
只可以对文件进行操作 - (文件的读取、写入)
强大的文件输写功能 – 内部是 byte[] 数组
劣势:已经被NIO取代
简单示例 – 文件的合并
public static void writeContent(String sourcePath, String targetPath) {
RandomAccessFile source = null;
RandomAccessFile target = null;
try {
// 1. 定义 文件流
source = new RandomAccessFile(sourcePath, "r");
target = new RandomAccessFile(targetPath, "rw");
// 2. 设置文件流的输入指针在文件末,而不是默认的文件头
target.seek(target.length());
// 3. 填充内容到目标文件末尾
byte[] buffer = new byte[2048];
int len = -1;
while( (len = source.read(buffer) ) != -1) {
target.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
source.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
target.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 测试代码
writeContent("C:\\Users\\lrc\\Desktop\\测试\\常见面试题.txt", "C:\\Users\\lrc\\Desktop\\测试\\项目测试SQL.sql");
运行结果
2.2.7 ZipOutputStream( 压缩 ), ZipInputStream( 解压 ) – 文件压缩
应用示例 - 压缩文件
// 传入你将需要压缩文件的绝对路径
public static void compress(String sourcePath) {
try {
// 1. 创建一个压缩流、并且如果没有关联的压缩文件,自动创建 -- 压缩后的文件在桌面上
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("C:\\Users\\lrc\\Desktop\\压缩.zip"));
// 2. 将即将压缩的文件名转为 File对象
File f = new File(sourcePath);
// 3. 调用递归函数
zipFile(zos, f, f.getName());
// 4. 压缩输出流关闭
zos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void zipFile(ZipOutputStream zos, File f, String prefix) throws IOException {
// 1. 获取当前File对象 是文件 则返回null 是目录则返回File数组
File[] files = f.listFiles();
// 2. 文件压缩
if (files == null) {
zos.putNextEntry(new ZipEntry(prefix));
InputStream is = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[1024];
int len = -1;
while ((len = bis.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}
bis.close();
// 3. 目录压缩
} else {
if(files.length == 0)
zos.putNextEntry(new ZipEntry(prefix + "/"));
for (File file : files) {
zipFile(zos, file, prefix + "/" + file.getName());
}
}
}
// 测试代码
compress("C:\\Users\\lrc\\Desktop\\Jaav-Web页面");
应用示例 - 解压缩文件
/**
* @Description 传入需要解压的文件的绝对路径,以解压文件名为目录在桌面上显示解压结果
*/
public static void decompress(String zipFileName) {
// 1. 获取zipFileName压缩文件的文件名,并以这个文件名为 压缩后文件的目录
int zipNameEnd = zipFileName.lastIndexOf(".");
int zipNameStart = zipFileName.lastIndexOf("/") != -1? zipFileName.lastIndexOf("/") : zipFileName.lastIndexOf("\\");
String directory = zipFileName.substring(zipNameStart+1, zipNameEnd);
String path = "C:\\Users\\lrc\\Desktop\\" + directory + "\\";
try {
// 2. 获取一个解压流对象
InputStream is = new FileInputStream(zipFileName);
ZipInputStream zis = new ZipInputStream(is);
ZipEntry entry = null;
File file = null;
// 3. 迭代进行遍历获取解压流的 zipEntry条目
while((entry = zis.getNextEntry())!=null && entry.isDirectory() == false) {
file = new File(path + entry.getName());
System.out.println(file.getPath());
// 3.1 重复的创建目录,不会覆盖目录中已存在的文件
file.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len = -1;
while((len = zis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.close();
}
zis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
2.3 应用
2.3.1 文件分割
public static void splitFile(String filePath, long splitLength) {
File sourceFile = new File(filePath);
// 1. 分割的文件数
int remainder = (int) (sourceFile.length() % splitLength);
int fileCount = (int) (sourceFile.length() / splitLength);
int num_file = remainder == 0 ? fileCount : fileCount + 1;
// 声明一个字节输入缓冲流
BufferedInputStream bis = null;
try {
InputStream is = new FileInputStream(sourceFile);
bis = new BufferedInputStream(is);
// 2. 生成num_file个切割文件
for (int i = 0; i < num_file; i++) {
// 3. 打印分割文件的绝对路径
System.out.println(sourceFile.getParent() + "\\s" + (i + 1) + " - " + sourceFile.getName());
OutputStream os = new FileOutputStream(
sourceFile.getParent() + "\\s" + (i + 1) + " - " + sourceFile.getName());
BufferedOutputStream bos = new BufferedOutputStream(os);
byte[] buffer = null;
int len = -1;
// 如果切割文件的字节数小于 2KB 则直接以一个切割文件字节数来缓冲数组
if (splitLength <= 2048) {
buffer = new byte[(int)splitLength];
if ((len = bis.read(buffer)) != -1)
bos.write(buffer, 0, len);
bos.close();
continue;
// 否则每次以2kB来进行切割文件的写操作
} else {
buffer = new byte[2048];
// 计算每个切割文件需要缓冲数组写操作的次数
int count = splitLength % 2048 == 0 ? (int) (splitLength / 2048) : (int) (splitLength / 2048) + 1;
for (int j = 0; j < count; j++) {
if ((len = bis.read(buffer)) != -1)
bos.write(buffer, 0, len);
}
bos.close();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 测试代码
// -- 每个文件分割成为8M
splitFile("D:\\谷歌\\下载的文件\\张国荣 - 童年时.flv", 1024*1024*8);
运行效果
2.3.3 文件合并 - sequenceInputStream
各种输入流的逻辑串联,依次读取流集合中每个流的信息,直至到最后一个流才结束
public static void mergeFile(String... sourcePaths) {
ArrayList<InputStream> lists = new ArrayList<InputStream>();
BufferedOutputStream bis = null;
SequenceInputStream sis = null;
try {
// 1. 将需要合并的文件放入list容器中
for (String sourcePath : sourcePaths) {
lists.add(new FileInputStream(sourcePath));
}
// 2. 序列输入流接受的参数是枚举类似的,故需要转型
Enumeration<InputStream> enums = Collections.enumeration(lists);
sis = new SequenceInputStream(enums);
// 3, 以第一个文件类型为标准,确定合并后的文件类型
String fileType = sourcePaths[0].substring((sourcePaths[0].lastIndexOf(".")));
// 4. 将合并后的文件输出到桌面上,并且 文件名 以 合并文件.fileType
bis = new BufferedOutputStream(
new FileOutputStream("C:\\Users\\lrc\\Desktop\\合并文件" + fileType));
byte[] buffer = new byte[2048];
int len = -1;
// 5. 将序列输入流的流文件依次写入到 合并文件输出流中
while((len = sis.read(buffer)) != -1) {
bis.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if( sis != null) {
try {
sis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if( bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 测试代码
String[] sourcePaths = {"D:\\谷歌\\下载的文件\\s1 - 张国荣 - 童年时.flv", "D:\\谷歌\\下载的文件\\s2 - 张国荣 - 童年时.flv", "D:\\谷歌\\下载的文件\\s3 - 张国荣 - 童年时.flv"};
mergeFile(sourcePaths);
运行效果
3. NIO流 - ( New input/output流 )
3.1 概念
NIO优势:
1. 传统流I/O是以单字节流进行传输,而新I/O是以数据块的形式进行传输
2. 所有数据都是用缓冲区进行的 — 重点
3. 缓冲区提供对数据的结构化访问 - 对每种基本数据类型都有对应的缓冲区
3.2 缓冲区方法
public static void main(String[] args) throws IOException {
IntBuffer ib = IntBuffer.allocate(10);
System.out.println(ib + "\n");
ib.put(1);
ib.put(2);
System.out.println(ib + "\n");
ib.flip();
System.out.println(ib + "\n");
if(ib.hasRemaining()) {
for(int i = 0; i < ib.remaining(); i++) {
System.out.println(ib.get(i));
}
}
System.out.println("");
ib.put(15);
ib.flip();
System.out.println(ib);
}
运行结果
3.3 Channel通道
通过Channel通道数据往缓冲区进行读写 - 我们不会在通道进行获取、读取数据
简单示例 - 文件复制 - 方式1
public static void copyFile(String sourcePath) {
File sourceFile = new File(sourcePath);
String copiedFile = "C:\\Users\\lrc\\Desktop\\" + sourceFile.getName();
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel fc1 = null;
FileChannel fc2 = null;
try {
// 1.新建文件输入输出流
fis = new FileInputStream(sourceFile);
fos = new FileOutputStream(copiedFile); //将文件复制到桌面
// 2. 获取与文件相连的通道
fc1 = fis.getChannel();
fc2 = fos.getChannel();
// 3. 新建一个2048字节的 字节缓冲区 -- 我们只操作这部分
ByteBuffer bb = ByteBuffer.allocate(2048);
// 4. 将 字节缓冲区的数据通过 Channel通道 填充文件
while(fc1.read(bb)!= -1) {
// 4.1 确定当前字节缓冲区limit已经填充元素的个数
bb.flip();
// 4.2 将字节缓冲区的数据填充入文件
fc2.write(bb);
// 4.3 将字节缓冲区的所有数据清空,恢复初始的状态
bb.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
// 5. 关闭流、以及通道
} finally {
try {
fc2.close();
fc1.close();
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
源码分析
简单示例 - 文件复制 - 方式2
public static void randomCopyFile(String sourcePath) {
File sourceFile = new File(sourcePath);
String copiedFile = "C:\\Users\\lrc\\Desktop\\" + sourceFile.getName();
RandomAccessFile raf_R = null;
RandomAccessFile raf_W = null;
FileChannel readC = null;
FileChannel writeC = null;
try {
// 1. 获取对应文件输入 随机存取输入输出流
raf_R = new RandomAccessFile(sourceFile, "r");
raf_W = new RandomAccessFile(copiedFile, "rw");
// 2. 根据1的输入输出流获取 字节通道
readC = raf_R.getChannel();
writeC = raf_W.getChannel();
long length = sourceFile.length();
// 3. 根据2的道道,获取对应的内存 字节缓冲区 -- 输入缓冲区已经是有内容的
MappedByteBuffer map_R = readC.map(MapMode.READ_ONLY, 0, length);
// 4. 注意输出缓冲区映射需要设置的跟复制文件的大小一样
MappedByteBuffer map_W = writeC.map(MapMode.READ_WRITE, 0, length);
// 5. 将映射文件的输入缓冲区内容,增添到输出缓冲区
if (map_R.hasRemaining()) {
for (int i = 0; i < map_R.remaining(); i++) {
map_W.put(map_R.get(i));
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 6. 通道关闭时,会将输出映射缓冲区数据块写入文件
writeC.close();
readC.close();
raf_W.close();
raf_R.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4 I/O性能
性能越往下越低