一、前言
当涉及到I/O(输入/输出)时,它是计算机编程中不可或缺的部分。I/O 是指计算机与外部世界之间的数据传输过程,涵盖了读取输入数据和向输出设备发送数据的操作。在编写应用程序时,我们经常需要与用户进行交互、读取和写入文件、处理网络数据等,这些都需要使用到I/O操作。本文将介绍Java中的I/O操作,并提供一些常用的Java I/O类和接口的示例。
二、什么是IO
首先IO就是由input和output这两个词构成,意思就是输入输出。那什么又是输入输出的过程呢?
输入就是通常涉及将数据从外部源(如硬盘、键盘、网络等)传输到计算机的内存中,以便程序可以对其进行处理和操。
输出则是将程序处理后的数据从内存发送到外部目标(如硬盘、屏幕、打印机、网络等)进行存储或显示。
简单来说输入是从硬盘传到内存,输出是从内存保存到硬盘。
类比一下就是硬盘是一本书,输入就是我们读取书上知识的过程,输出是将从书上学到的东西写下来或记录下来。
三、字节流和字符流
字节流:
InputStream
:字节输入流,用于读取字节数据。
OutputStream
:字节输出流,用于写入字节数据。
字节流以一个字节(byte)为一个字节为单位进行读写操作,它们适用于处理二进制数据,如图像、音频、视频等。
字符流:
Reader
:字符输入流,用于读取字符数据。
Writer
:字符输出流,用于写入字符数据。
字符流以字符(char)为单位进行读写操作,它们适用于处理文本数据。例如普通文本文件(.txt),CSV文件,XML文件,JSON文件等。对于.doc文件等特定格式的文档,字符流读写不了。
总的来说字节流是万能的,适用于所有情况,字符流有一些限制。如图是Java帮助文档中IO的一些类。
关于Java帮助文档中IO的所有类中一般以Stream结尾的都是字节流,以Reaer或Writer结尾的都是字符流,以Stream加其他后缀的(例如;StreamReader)是字节流转字符流---统称为转换流。
四、文件I/O
以下是关于文件读取的代码示例:
import java.io.*;
public class IOplus1 {
public static void main(String[] args) {
String pathname = "D:\\One.java";
String outname = "D:\\One1.java";
FileInputStream fis = null;
FileOutputStream fos = null;
byte[] bytes = new byte[10];
try {
fis = new FileInputStream(pathname);
fos = new FileOutputStream(outname);
int read = fis.read(bytes);
while (read != -1) {
fos.write(bytes, 0, read);
read = fis.read(bytes);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
因为这个代码是以字节流读取的,所以打印出来的是,输出的是每个字节的 ASCII 值。如下:
119 111 99 101 110 105 109 97 -27 -109
使用字符流读取时将FileInputStream改为FileReader,FileOutputStream改为FileWriter就行如下:
import java.io.*;
public class IOplus1 {
public static void main(String[] args) {
String pathname = "D:\\One.java";
String outname = "D:\\One1.java";
FileReader reader = null;
FileWriter writer = null;
char[] buffer = new char[10];
try {
reader = new FileReader(pathname);
writer = new FileWriter(outname);
int read = reader.read(buffer);
for (int i = 0; i < buffer.length; i++) {
System.out.print(buffer[i] + " ");
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这个能输出你正常打出来的字符。
1、在如上代码中,整个IO的重要的地方是其中的bytes数组,这个数组的作用是将硬盘上的文件以一个byte一个byte的形式读取到内存。
而且bytes数组是用来存储从文件中读取的字节数据的缓冲区。它的作用是一次性读取多个字节到内存中,提高读取的效率。具体来说,bytes数组的大小是10,表示一次最多读取10个字节。通过调用fis.read(bytes)
方法,将读取到的字节数据存储在bytes数组中,并返回实际读取的字节数。然后可以对这些字节数据进行处理,比如写入到输出文件中。
简单来说这个bytes就是个中转站,他不会进行真正的存储,而是在使用时临时存储从输入流中读取的字节数据。
2、还有就是bytes
数组的大小会影响每次读取的字节数。较小的数组大小意味着每次读取的字节数较少,可能需要更多的读取操作才能读取整个文件内容。这会增加读取文件的次数,从而导致一定的性能损耗。
相反,较大的数组大小可以一次性读取更多的字节,减少了读取操作的次数,从而提高了读取文件的速度。
然而,选择合适的数组大小并不意味着越大越好。如果数组大小超过文件的实际大小,会导致读取的字节中包含无效数据。因此,合理的做法是根据实际需要和文件大小选择一个适中的数组大小。
IO的使用总结起来就四个方面:确定源、打开流、操作流、关闭流。
五、异常处理和资源关闭
1、异常处理:在上面的的代码中的IO都进行了异常处理操作在IO操作中,常见的异常是IOException
,它是所有IO异常的基类。在代码中,可以使用try-catch
语句块来捕获并处理异常。例如:
try {
// 执行IO操作的代码
} catch (IOException e) {
// 处理异常的代码
}
在catch
块中可以根据实际需求进行异常处理,比如打印错误信息、记录日志、进行恢复操作等。
2、资源关闭:在进行IO操作后,需要及时关闭相关的资源,以释放系统资源并确保数据的完整性。常见的资源包括文件流、网络连接、数据库连接等。对于Java IO操作,可以在finally
块中关闭资源,以确保无论是否发生异常,资源都会得到正确关闭。例如:
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 执行IO操作的代码
} catch (IOException e) {
// 处理异常的代码
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// 处理关闭异常的代码
}
}
}
在处理输入流和输出流时,应该按照先创建的后关闭的顺序进行操作。在某些情况下,输出流可能会依赖于输入流的状态或数据。如果先关闭了输入流,可能会导致输出流无法正常写入数据或出现异常。
可以将输入流和输出流类比为水流管道。
假设你有一个输入水管和一个输出水管,它们连接着水源和水池。你要从水源中获取水,并将其送到水池中。
现在,假设你先关闭了输入水管,再关闭输出水管。这意味着你先切断了从水源获取水的途径,然后再切断了将水送到水池的途径。结果是,你无法获取水,也无法将水送到水池中。
相反,如果你先关闭了输出水管,再关闭输入水管,那么你会先切断将水送到水池的途径,然后再切断从水源获取水的途径。这样,你仍然可以获取水,但无法将水送到水池中。
六、性能优化
缓冲流(Buffered Stream):在之前的代码中,我们知道里面的bytes数组是个中转站,我们可以用其他东西取代。
当我们使用缓冲流进行读取操作时,它会从底层的字节流或字符流中读取一定数量的数据到缓冲区中,然后我们可以从缓冲区中逐个读取数据。这样可以减少对底层流的直接读取次数,提高读取效率。
同样地,当我们使用缓冲流进行写入操作时,它会先将数据写入到缓冲区中,然后在适当的时机将缓冲区中的数据一次性写入到底层流中。这样可以减少对底层流的直接写入次数,提高写入效率。
缓冲流的存在主要是为了提高IO操作的效率,通过批量读取和写入数据来减少与底层流的交互次数。这样可以减少IO操作的开销,提高程序的性能和响应速度。
示例:
import java.io.*;
public class IO{
public static void main(String[] args) throws IOException {
String sourceFile = "D:\\One.java";
String destinationFile = "D:\\One1.java";
BufferedInputStream fis = null;
BufferedOutputStream fos = null;
byte[] buffer = new byte[1024];
try {
fis = new BufferedInputStream(new FileInputStream(sourceFile));
fos = new BufferedOutputStream(new FileOutputStream(destinationFile));
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("File copied successfully.");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} finally {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
}
}
}
这个是一个文件复制代码,使用了缓冲流,这样可以进一步提高读取文件的性能,因为缓冲流内部维护了一个缓冲区,可以一次性读取多个字节,减少了与磁盘的交互次数。
关于这种IO的复制代码,它不仅仅可以复制一些文档和文件,还可以随便在浏览器搜索个什么图片保存下来,可以复制这个图片。
关于这个复制,如果我们能连接到其他的电脑服务器,有其地址,不也可以复制另一台电脑的东西吗。所以下载是和这个有关联的。
下载指从网络上获取文件到本地计算机的过程。下载通常涉及与服务器建立连接、发送请求、接收响应和获取文件内容等步骤。在下载过程中,可以使用字节流来读取网络数据,然后将数据写入本地文件中,以实现文件的下载。
复制和下载都依赖于文件的读取和写入操作,但下载通常还涉及网络连接和与服务器的通信。复制可以在本地设备上进行,而下载则是从网络获取文件到本地计算机。
七、总结
通过本文介绍,希望读者能够了解IO操作的基本概念和使用方法。可能介绍不是非常全面,希望能起到帮助。大家一起学习愉快。