Loading... IO这块其实相当于是工具类,一般来说在企业开发中基本已经集成了各种IOUtils了。可能更多的就是在文件上传/报表中用缓冲流进行读写操作。 (4399校招中就有一道IO拷贝的手写代码题,但感觉更多的是考研对于异常和流关闭的处理) <!--more--> ## File **`File`**:主要是针对**文件创建、删除文件、获取文件描述符**,包括文件夹的操作。 ```java /** * 主要包含:文件创建、删除文件、获取文件描述符 * * @author CaiTianXin * @date 2021/3/15 9:48 */ public class FileDemo { public static void main(String[] args) { File file = new File("D://abc.txt"); // 创建文件路径,可以是// \\ 或 / // 自适应路径分隔符,Windows是; Linux是: System.out.println(File.pathSeparator); // 自适应路径名称分隔符,Windows是\ Linux是/ System.out.println(File.separator); if (file.exists()) { // 判断文件是否存在 file.delete(); // 删除文件,彻底删除 不过回收站 System.out.println("删除成功!"); } else { try { file.createNewFile(); // 创建文件 System.out.println("创建成功!"); // 获取文件描述 // 如果FIle("绝对路径"):getPath() 和getAbsolutePath()的结果一致,打印的都是绝对路径 // 如果FIle("相对路径"):getPath() 打印相对路径;getAbsolutePath()打印绝对路径 System.out.println("绝对路径:" + file.getPath()); System.out.println("相对路径:" + file.getAbsolutePath()); System.out.println("文件名称:" + file.getName()); System.out.println("文件大小:" + file.length()); System.out.println(file.isFile() ? "文件" : "非文件"); System.out.println(file.isDirectory() ? "目录" : "非目录"); } catch (IOException e) { e.printStackTrace(); } } } } ``` **常用方法:**  --- ## 流 ### 流的划分 流是一种FIFO的数据结构。 IO类都是从InputStream、OutputStream、Reader、Writer慢慢衍生的。而且大部分源码都是打上 `native`标志,说明底层是C/c++实现的。 按流向来划分的话,它以内存为基准分为:**输入流**(文件→内存)和**输出流**(内存→文件)。 按类型来划分的话,可以分为:**字节流**和**字符流**两大类,其中又包含了**缓冲流**和**二进制流**。  按照操作对象来划分的话,又可以分为以下这几种大类:  --- ### 字节流 ```java /** * 字节流 * abc.txt -> 内存 -> xyz.txt * * @author CaiTianXin * @date 2021/3/15 11:14 */ public class ByteCopyDemo { public static void main(String[] args) { InputStream in = null; OutputStream out = null; try { // 输入流:abc.txt -> 内存 in = new FileInputStream("D://abc.txt"); // 输出流:内存 -> xyz.txt out = new FileOutputStream("D://xyz.txt"); // 一次读取开辟的内存大小 10字节,开辟过大不仅耗内存还会出现空格填充占位 byte[] buf = new byte[10]; // 作为每次从内存写入时的长度,防止最后一次携带没有被覆盖的脏数据 int len; while ((len = in.read(buf)) != -1) { out.write(buf, 0, len); } System.out.println("拷贝成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 按嵌套顺序关闭流 try{ if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` **常用方法:**   #### 子类说明 **InputStream:** * **`FilelnputStream`文件输入流**: FilelnputStream类创建一个能从文件读取字节的 InputStream类 * **`ByteArrayInputStream`字节数组输入流**: 把内存中的一个缓冲区作为InputStream 使用 * **`PipedInputStream`管道输入流**: 实现了pipe管道的概念,主要在线程中使用 * **`SequencelnputStream`顺序输入流**: 把多个InputStream合并为一个InputStream * **`FilterOutputStream`过滤输入流**: 其他输入流的包装 * **`objectIlnputStream`反序列化输入流**: 将之前使用ObjectOutputStream 序列化的原始数据恢复为对象,以流的方式读取对象 * **`DatalnputStream`**: 数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。 * **`PushbackInputStream`推回输入流**: 缓冲的一个新颖的用法是实现推回(pushback)。Pushback 用于输入流允许字节被读取然后返回到流。 **OutputStream:** * **`FileOutputStream`文件输出流**: 该类实现了一个输出流,其数据写入文件。 * **`ByteArrayOutputStream`字节数组输出流**: 该类实现了一个输出流,其数据被写入由byte数组充当的缓冲区,缓冲区会随着数据的不断写入而自动增长。 * **`PipedOutputStream`管道输出流**: 管道的输出流,是管道的发送端。 * **`ObjectOutputStream`基本类型输出流**: 该类将实现了序列化的对象序列化后写入指定地方。 * **`FilterOutputStream`过滤输出流**: 其他输出流的包装。 * **`PrintStream`打印流**: 通过PrintStream可以将文字打印到文件或者网络中去。 * **`DataOutputStream`**: 数据输出流允许应用程序以与机器无关方式向底层输出流中写入基本Java数据类型。 ### 字符流 ```java /** * 字符流(遇到中文乱码记得txt另存为选择utf-8) * 张三.txt -> 内存修改 -> 张三2.txt * * @author CaiTianXin * @date 2021/3/15 13:50 */ public class CharacterCopyDemo { public static void main(String[] args) { Reader reader = null; Writer writer = null; try { reader = new FileReader("D://张三.txt"); writer = new FileWriter("D://张三2.txt"); char[] buf = new char[4]; StringBuffer sb = new StringBuffer(); int len; while ((len = reader.read(buf)) != -1) { sb.append(buf, 0, len); } System.out.println(sb); String content = sb.toString(); content = content.replace("{name}", "张三") .replace("{age}", "23"); writer.write(content); } catch (IOException e) { e.printStackTrace(); } finally { try { if (writer != null) { writer.close(); } if (reader != null) { reader.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` **常用方法:**   #### 子类说明 **Reader:** * **`FileReader`文件字符输入流**: 把文件转换为字符流读入 * **`CharArrayReader`字符数组输入流:** 是一个把字符数组作为源的输入流的实现 * **`BufferedReader`缓冲区输入流**: BufferedReader类从字符输入流中读取文本并缓冲字符,以便有效地读取字符,数组和行 * **`PushbackReader`**: PushbackReader类允许一个或多个字符被送回输入流。 * **`PipedReader`管道输入流**: 主要用途也是在线程间通讯,不过这个可以用来传输字符 **Writer:** * **`FileWriter`字符输出流**: FileWriter 创建一个可以写文件的Writer类。 * **`CharArrayWriter`字符数组输出流**: CharArrayWriter 实现了以数组作为目标的输出流。 * **`BufferedWriter`缓冲区输出流**: BufferedWriter是一个增加了flush()方法的Writer。flush()方法可以用来确保数据缓冲器确实被写到实际的输出流。 * **`PrintWriter`**: PrintWriter本质上是 PrintStream的字符形式的版本。 * **`PipedWriter`管道输出流**: 主要用途也是在线程间通讯,不过这个可以用来传输字符 ### 二进制流 ```java /** * 二进制流(装饰模式) * * @author CaiTianXin * @date 2021/3/15 14:55 */ public class BinaryCopyDemo { public static void main(String[] args) { InputStream in = null; OutputStream out = null; DataInputStream dataInput = null; DataOutputStream dataOutput = null; try { in = new FileInputStream("D://abc.txt"); out = new FileOutputStream("D://xyz.txt"); dataInput = new DataInputStream(in); dataOutput = new DataOutputStream(out); byte[] buf = new byte[10]; int len; while ((len = dataInput.read(buf)) != -1) { dataOutput.write(buf, 0, len); } System.out.println("拷贝成功!"); } catch (IOException e) { e.printStackTrace(); } finally { // 按嵌套顺序关闭流 try{ if (dataOutput != null) { dataOutput.close(); } if (dataInput != null) { dataInput.close(); } if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` ### 缓冲流 ```java /** * 缓冲流(装饰模式) * * @author CaiTianXin * @date 2021/3/15 14:43 */ public class BufferCopyDemo { public static void main(String[] args) { Reader reader = null; Writer writer = null; BufferedReader br = null; BufferedWriter bw = null; try { reader = new FileReader("D://张三.txt"); writer = new FileWriter("D://张三2.txt"); // 默认缓冲区大小(一行) br = new BufferedReader(reader); bw = new BufferedWriter(writer); StringBuffer sb = new StringBuffer(); String line; // 按行读取 while ((line = br.readLine()) != null) { sb.append(line); } System.out.println(sb); String content = sb.toString(); content = content.replace("{name}", "张三") .replace("{age}", "23"); bw.write(content); } catch (IOException e) { e.printStackTrace(); } finally { try { if (bw != null) { bw.close(); } if (br != null) { br.close(); } if (writer != null) { writer.close(); } if (reader != null) { reader.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` ## 网络编程-Socket **网络编程**是指使用计算机网络进行数据传输和通信的编程技术。它涉及到在应用程序中使用网络协议和API来实现数据交换。 **网络协议**是一组规定了数据交换格式、通信方式和行为的约定。它定义了不同设备之间如何建立连接、发送和接收数据的规则。常见的网络通讯协议包括TCP/IP(Transmission Control Protocol/Internet Protocol)、HTTP(Hypertext Transfer Protocol)和SMTP(Simple Mail Transfer Protocol)等。 `TCP协议`:面向连接的、可靠的(不丢失、不重复、有序)、基于字节流的传输通信协议,传输速度相对慢。TCP使用三次握手建立连接,以及序列号、确认应答和重传等机制来确保数据的可靠性和完整性。TCP适用于需要可靠传输的应用场景,例如文件传输、电子邮件和网页浏览。 `UDP协议`:面向无连接的、不可靠的传输通信协议,传输速度相对快。UDP将数据分成较小的数据包进行传输,并且没有建立连接的过程。UDP不提供拥塞控制、顺序控制、重传机制等可靠性保证,但由于其简单和低延迟的特点,UDP常用于实时通信、流媒体传输和在线游戏等应用场景。 **Socket**(套接字):基于TCP协议,可以提供双向安全连接的网络通信。Socket需要借助于数据流(字节流)来完成数据的传递工作  ### TCP ```java package demo.io; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; /** * 学生 * * @author caitianxin * @date 2023/09/08 */ @Data @AllArgsConstructor public class Student implements Serializable { private Long id; private String name; private Integer age; } ``` ```java package demo.io; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.net.Socket; /** * 客户端01 * * @author caitianxin * @date 2023/09/08 */ public class MyClient01 { public static void main(String[] args) { Socket socket = null; OutputStream out = null; ObjectOutputStream oos = null; try { Student student = new Student(1L, "小明01", 18); socket = new Socket("127.0.0.1", 6677); out = socket.getOutputStream(); oos = new ObjectOutputStream(out); oos.writeObject(student); InputStream in = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = in.read(bytes); System.out.println("接收服务端反馈:" + new String(bytes, 0, len)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (oos != null) { oos.close(); } if (out != null) { out.close(); } if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` ```java package demo.io; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.net.Socket; /** * 客户端02 * * @author caitianxin * @date 2023/09/08 */ public class MyClient02 { public static void main(String[] args) { Socket socket = null; OutputStream out = null; ObjectOutputStream oos = null; try { Student student = new Student(1L, "小明02", 18); socket = new Socket("127.0.0.1", 6677); out = socket.getOutputStream(); oos = new ObjectOutputStream(out); oos.writeObject(student); InputStream in = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = in.read(bytes); System.out.println("接收服务端反馈:" + new String(bytes, 0, len)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (oos != null) { oos.close(); } if (out != null) { out.close(); } if (socket != null) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` ```java package demo.io; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * 服务端 * * @author caitianxin * @date 2023/09/08 */ public class MyServer { public static void main(String[] args) { try { // 服务端启动 ServerSocket serverSocket = new ServerSocket(6677); while (true) { // IO阻塞,接受客户端请求 Socket socket = serverSocket.accept(); System.out.println("服务端检测到客户端连接成功"); // 每个线程专门处理一个客户端socket // 这种方法不仅会造成IO堵塞,也会浪费线程资源,仅作为学习demo ServerThread serverThread = new ServerThread(socket); serverThread.start(); } } catch (IOException e) { e.printStackTrace(); } } static class ServerThread extends Thread { Socket socket; public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { InputStream in = null; ObjectInputStream ois = null; OutputStream out = null; try { // 接收客户端数据(客户端—>服务端) in = socket.getInputStream(); ois = new ObjectInputStream(in); Student student = (Student) ois.readObject(); System.out.println(student); socket.shutdownInput(); // 给客户端反馈(服务端->客户端) out = socket.getOutputStream(); out.write("已收到....".getBytes()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } if (ois != null) { ois.close(); } if (in != null) { in.close(); } socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ``` ```properties 服务端检测到客户端连接成功 Student(id=1, name=小明01, age=18) 服务端检测到客户端连接成功 Student(id=1, name=小明02, age=18) 接收服务端反馈:已收到.... ``` 上述代码仅作为学习使用,存在一些潜在的问题和弊端: 1. 每个客户端连接都会创建一个新的线程:对于大量并发的客户端请求,会创建大量线程,消耗系统资源。这种方式对于高并发场景不适用。 2. 没有使用线程池管理线程:线程创建和销毁也会带来一定的开销,通过使用线程池可以复用线程,提高效率,减少资源消耗。 3. 缺乏异常处理机制:在IO操作中可能会出现各种异常,但是代码没有处理这些异常。应该添加适当的异常处理机制,以确保代码的可靠性和稳定性。 4. 数据传输使用了Java的序列化机制:尽管Java序列化是方便的,但它也具有一些安全风险,并且在跨平台和跨语言通信时可能有兼容性问题。建议考虑使用更通用、轻量级的数据格式,如JSON或协议缓冲区。 5. 代码缺乏安全性考虑:没有进行身份验证、输入验证和过滤、加密传输等安全性措施。在实际的应用程序中,必须仔细考虑和实施这些措施来保护应用程序免受攻击。 ### UDP ```java package demo.io; import java.io.IOException; import java.net.*; /** * UDP 发送方 * * @author caitianxin * @date 2023/09/08 */ public class MySend { public static void main(String[] args) { // DatagramPacket(数据):封装了数据报的数据、数据长度、目标地址和目标端口 // DatagramSocket(收发器):接收和发送DatagramPacket中封装好的数据 // 使用try-with-resources简化资源的管理和释放 try (DatagramSocket ds = new DatagramSocket()) { // 创建一个InetAddress对象 InetAddress address = InetAddress.getByName("127.0.0.1"); String msg = "UDP_send"; DatagramPacket dp = new DatagramPacket(msg.getBytes(), msg.length(), address, 6677); ds.send(dp); } catch (IOException e) { e.printStackTrace(); } } } ``` ```java package demo.io; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * 服务端 * * @author caitianxin * @date 2023/09/08 */ public class MyServer { public static void main(String[] args) { try { // 服务端启动 ServerSocket serverSocket = new ServerSocket(6677); while (true) { // IO阻塞,接受客户端请求 Socket socket = serverSocket.accept(); System.out.println("服务端检测到客户端连接成功"); // 每个线程专门处理一个客户端socket // 这种方法不仅会造成IO堵塞,也会浪费线程资源,仅作为学习demo ServerThread serverThread = new ServerThread(socket); serverThread.start(); } } catch (IOException e) { e.printStackTrace(); } } static class ServerThread extends Thread { Socket socket; public ServerThread(Socket socket) { this.socket = socket; } @Override public void run() { InputStream in = null; ObjectInputStream ois = null; OutputStream out = null; try { // 接收客户端数据(客户端—>服务端) in = socket.getInputStream(); ois = new ObjectInputStream(in); Student student = (Student) ois.readObject(); System.out.println(student); socket.shutdownInput(); // 给客户端反馈(服务端->客户端) out = socket.getOutputStream(); out.write("已收到....".getBytes()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } if (ois != null) { ois.close(); } if (in != null) { in.close(); } socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ``` ```properties 已连接到目标 VM, 地址: ''127.0.0.1:63052',传输: '套接字'' 接收到的数据:UDP_send 显示发送方的信息:127.0.0.1 与目标 VM 断开连接, 地址为: ''127.0.0.1:63052',传输: '套接字'' 已连接到目标 VM, 地址: ''127.0.0.1:63094',传输: '套接字'' 与目标 VM 断开连接, 地址为: ''127.0.0.1:63094',传输: '套接字'' ``` 同样以上代码仅作为学习演示,存在一些潜在的问题和弊端: 1. 缺乏错误处理机制:代码没有处理可能出现的异常情况,如网络连接中断、数据包丢失或损坏等。应该添加适当的错误处理机制,以确保代码的可靠性和健壮性。 2. 没有考虑数据包大小限制:UDP协议对于发送的数据包有大小限制(通常为64KB)。如果发送的数据包超过了这个限制,可能会导致数据丢失或截断。需要根据实际需求和场景,合理控制数据包大小,并进行必要的分片和重组。 3. 缺乏数据校验和确认机制:UDP是一种不可靠的传输协议,它不提供数据完整性和传输确认机制。这意味着无法保证数据被正确接收。在实际应用中,可以考虑添加校验和确认机制来提高数据传输的可靠性。 4. 代码中使用固定的缓冲区大小:代码中使用了固定的缓冲区大小,如果接收到的数据包超过了缓冲区的大小,可能会导致数据丢失或截断。推荐使用动态分配的缓冲区,以适应不同大小的数据包。 Last modification:September 8, 2023 © Allow specification reprint Like 0 喵ฅฅ