Java IO流全解析:从基础到实战,彻底吃透文件与数据操作
在Java开发中,IO(Input/Output)流是核心基础知识点之一,贯穿于文件操作、网络通信、数据传输等各类场景——无论是读取本地文件、写入日志,还是接收网络数据,都离不开IO流的支持。
本文将从IO流的本质、核心分类、核心类用法,到实战案例、资源释放、避坑指南,层层递进,用通俗的语言+可直接复用的代码,帮你彻底吃透Java IO流,轻松应对开发中的各类IO操作。
一、先搞懂:什么是Java IO流?
IO流的本质是「数据的传输通道」,用于实现程序与外部设备(文件、键盘、网络、数据库等)之间的数据读取(Input)和写入(Output)。简单来说,IO流就像一根“水管”,数据就是“水流”,通过这根“水管”,数据可以从一个地方传输到另一个地方。
Java IO流的设计遵循「面向对象」思想,将不同的输入输出场景抽象为不同的流类,统一提供标准的读写方法,开发者无需关注底层硬件细节,只需调用对应方法即可完成数据传输。
1.1 IO流的核心特点
-
方向性:分为输入流(Input Stream)和输出流(Output Stream),输入流是“从外部设备读入数据到程序”,输出流是“从程序写入数据到外部设备”;
-
字节导向/字符导向:分为字节流(以字节为单位传输,适用于所有数据)和字符流(以字符为单位传输,适用于文本数据);
-
可叠加性:处理流可以叠加在节点流之上,增强读写功能(如缓冲、编码转换);
-
资源需要手动释放:IO流属于稀缺资源(占用系统文件句柄、网络连接等),使用后必须手动关闭,否则会导致资源泄漏。
1.2 IO流的核心分类(重中之重)
Java IO流的分类有两种核心维度,必须牢记,这是理解所有IO类的基础:
维度1:按数据流向分类(最基础)
-
输入流(Input Stream):数据从外部设备 → 程序(读操作),核心是“读”;
-
输出流(Output Stream):数据从程序 → 外部设备(写操作),核心是“写”。
补充:判断是输入流还是输出流,以程序为参照物——数据进入程序就是输入,离开程序就是输出。例如:读取本地文件到程序,用输入流;将程序中的数据写入文件,用输出流。
维度2:按数据传输单位分类(最常用)
-
字节流:以字节(byte)为单位传输数据(1字节=8位),适用于所有类型数据(文本、图片、视频、音频等),核心父类是
InputStream(输入字节流)和OutputStream(输出字节流); -
字符流:以字符(char)为单位传输数据(1字符=2字节,适配Unicode编码),仅适用于文本数据(.txt、.java等),核心父类是
Reader(输入字符流)和Writer(输出字符流)。
维度3:按流的功能分类(实战常用)
-
节点流(底层流):直接连接外部设备(文件、键盘、网络),是IO流的基础,只能直接读写数据,功能简单(如FileInputStream、FileReader);
-
处理流(包装流):不能直接连接外部设备,必须包装在节点流或其他处理流之上,用于增强读写功能(如缓冲、编码转换、对象序列化),功能强大(如BufferedInputStream、BufferedReader)。
记忆技巧:节点流是“直接干活的”,处理流是“给节点流赋能的”;字节流是“万能的”(所有数据都能处理),字符流是“专用的”(只处理文本)。
二、Java IO流核心体系(一张图看懂所有流)
Java IO流的类非常多,但都围绕4个核心父类展开,所有具体的IO流类都是这4个父类的子类。掌握这个体系,就能快速理清所有IO流的关系,避免混淆。
2.1 核心体系结构图(重点记)
2.2 核心父类的常用方法(必记)
4个顶层父类都是抽象类,无法直接实例化,但其子类会继承并实现它们的核心方法,掌握这些方法,就能快速上手所有具体的IO流。
(1)字节流核心方法
| 父类 | 核心方法(读/写) | 说明 |
|---|---|---|
| InputStream(输入字节流) | int read():读取一个字节int read(byte[] b):读取多个字节到字节数组void close():关闭流,释放资源 | read()返回-1表示读取到末尾;读取字节时,返回值是字节的ASCII码(0-255) |
| OutputStream(输出字节流) | void write(int b):写入一个字节void write(byte[] b):写入字节数组void flush():刷新缓冲区(强制写入)void close():关闭流 | write(int b)时,传入的是字节的ASCII码;缓冲流必须flush()或close(),否则数据可能未写入 |
(2)字符流核心方法
| 父类 | 核心方法(读/写) | 说明 |
|---|---|---|
| Reader(输入字符流) | int read():读取一个字符int read(char[] cbuf):读取多个字符到字符数组void close():关闭流 | read()返回-1表示读取到末尾;返回值是字符的Unicode码 |
| Writer(输出字符流) | void write(int c):写入一个字符void write(char[] cbuf):写入字符数组void write(String str):写入字符串(最常用)void flush():刷新缓冲区void close():关闭流 | 支持直接写入字符串,无需手动转换,适合文本处理;缓冲流需flush() |
三、实战入门:字节流的使用(万能流,必学)
字节流是Java IO流的基础,适用于所有类型的数据(文本、图片、视频等),核心使用场景是「文件读写」。以下是最常用的字节流(节点流+处理流)实战用法,代码可直接复用。
3.1 节点流:FileInputStream + FileOutputStream(文件读写)
FileInputStream(文件输入字节流):直接读取本地文件中的字节数据; FileOutputStream(文件输出字节流):直接将字节数据写入本地文件。
3.1.1 用字节流读取文件(FileInputStream)
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamDemo {
public static void main(String[] args) {
// 1. 声明流对象(必须初始化,或在finally中关闭,避免资源泄漏)
FileInputStream fis = null;
try {
// 2. 创建流对象,指定要读取的文件路径(相对路径/绝对路径)
// 相对路径:默认在项目根目录下
fis = new FileInputStream("test.txt");
// 3. 读取数据(两种方式)
// 方式1:读取单个字节(效率低,适合小文件)
int readByte;
while ((readByte = fis.read()) != -1) { // read()返回-1表示读取完毕
// 将字节转换为字符(适合文本文件)
System.out.print((char) readByte);
}
// 方式2:读取多个字节到字节数组(效率高,推荐)
// byte[] buffer = new byte[1024]; // 缓冲区大小(1024字节=1KB,可调整)
// int len; // 实际读取到的字节数
// while ((len = fis.read(buffer)) != -1) {
// // 将字节数组转换为字符串,读取多少转换多少
// System.out.print(new String(buffer, 0, len));
// }
} catch (IOException e) {
// 捕获IO异常(文件不存在、权限不足等)
e.printStackTrace();
} finally {
// 4. 关闭流,释放资源(必须执行,即使出现异常)
try {
if (fis != null) { // 避免空指针异常(流创建失败时fis为null)
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.1.2 用字节流写入文件(FileOutputStream)
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
// 2. 创建流对象,指定写入的文件路径
// 第二个参数:true表示“追加写入”,false表示“覆盖写入”(默认false)
fos = new FileOutputStream("test.txt", true);
// 3. 写入数据(三种方式)
// 方式1:写入单个字节(ASCII码)
fos.write(97); // 写入字符'a'(ASCII码97)
// 方式2:写入字节数组
byte[] buffer = "Hello Java IO".getBytes(); // 字符串转字节数组
fos.write(buffer);
// 方式3:写入字节数组的一部分(从索引0开始,写入5个字节)
fos.write(buffer, 0, 5);
// 4. 刷新缓冲区(字节流的缓冲处理,确保数据写入文件)
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭流
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.2 处理流:BufferedInputStream + BufferedOutputStream(缓冲流,提升效率)
缓冲流是最常用的处理流,其核心作用是「设置缓冲区」,减少磁盘IO次数(内存读写比磁盘读写快得多),大幅提升读写效率。使用时,必须包装在节点流(如FileInputStream)之上。
3.2.1 缓冲字节流实战(文件复制)
场景:用缓冲字节流复制一张图片(或视频、文本),体现缓冲流的效率优势。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedStreamDemo {
public static void main(String[] args) {
// 声明流对象(缓冲流包装节点流)
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1. 创建节点流(连接文件)
FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("target.jpg");
// 2. 创建缓冲流,包装节点流(缓冲区默认8KB,可手动指定大小)
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
// 3. 复制数据(高效读取)
byte[] buffer = new byte[1024]; // 自定义缓冲区大小
int len;
// 读取数据到缓冲区,再写入目标文件
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
// 4. 刷新缓冲区(缓冲流必须刷新,否则数据可能残留缓冲区)
bos.flush();
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭流(关闭缓冲流时,会自动关闭其包装的节点流,无需手动关闭fis、fos)
try {
if (bos != null) bos.close();
if (bis != null) bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
补充:关闭流时,「先关闭输出流,再关闭输入流」,避免资源泄漏;缓冲流关闭后,其包装的节点流会自动关闭,无需重复关闭。
四、实战进阶:字符流的使用(文本专用)
字符流专门用于处理文本数据(.txt、.java、.xml等),支持直接读写字符串,无需手动转换字节,同时能处理字符编码(如UTF-8、GBK),避免中文乱码问题。
4.1 节点流:FileReader + FileWriter(文本文件读写)
FileReader(文件输入字符流):读取文本文件中的字符数据; FileWriter(文件输出字符流):将字符数据写入文本文件,支持直接写入字符串。
4.1.1 用字符流读取文本文件(FileReader)
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) {
FileReader fr = null;
try {
// 创建字符流对象,指定文本文件路径
fr = new FileReader("test.txt");
// 读取数据(两种方式)
// 方式1:读取单个字符
int readChar;
while ((readChar = fr.read()) != -1) {
System.out.print((char) readChar);
}
// 方式2:读取多个字符到字符数组(推荐,效率高)
// char[] buffer = new char[1024];
// int len;
// while ((len = fr.read(buffer)) != -1) {
// System.out.print(new String(buffer, 0, len));
// }
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if (fr != null) fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.1.2 用字符流写入文本文件(FileWriter)
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) {
FileWriter fw = null;
try {
// 创建字符流对象,指定写入路径,true表示追加写入
fw = new FileWriter("test.txt", true);
// 写入数据(三种方式)
// 方式1:写入单个字符
fw.write('你');
// 方式2:写入字符数组
char[] buffer = {'好', ',', 'J', 'a', 'v', 'a'};
fw.write(buffer);
// 方式3:写入字符串(最常用)
fw.write("\nHello Java IO 字符流"); // \n是换行符
// 刷新缓冲区(字符流必须刷新,否则数据无法写入)
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.2 处理流:BufferedReader + BufferedWriter(缓冲字符流,文本高效读写)
缓冲字符流在字符流的基础上增加了缓冲区,提升读写效率,同时提供了更便捷的方法(如BufferedReader的readLine()方法,读取一行文本),是文本处理的首选。
4.2.1 缓冲字符流实战(读取一行文本)
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderDemo {
public static void main(String[] args) {
BufferedReader br = null;
try {
// 缓冲字符流包装节点流FileReader
br = new BufferedReader(new FileReader("test.txt"));
String line;
// readLine():读取一行文本,返回null表示读取到末尾(不包含换行符)
while ((line = br.readLine()) != null) {
System.out.println(line); // 打印每一行,手动换行
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.2.2 缓冲字符流实战(写入换行)
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterDemo {
public static void main(String[] args) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter("test.txt", true));
// 写入字符串,并用newLine()方法换行(跨平台兼容,Windows\n、Linux\n)
bw.write("第一行文本");
bw.newLine(); // 换行(比手动写\n更规范)
bw.write("第二行文本");
bw.newLine();
bw.flush(); // 必须刷新
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.3 处理流:InputStreamReader + OutputStreamWriter(字节流转字符流,处理编码)
这是非常重要的处理流,用于「字节流和字符流的转换」,核心作用是处理字符编码(如UTF-8、GBK),避免中文乱码。
问题场景:当文本文件的编码是UTF-8,而系统默认编码是GBK时,用FileReader读取会出现中文乱码——此时就需要用InputStreamReader指定编码格式。
4.3.1 编码转换实战(解决中文乱码)
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
public static void main(String[] args) {
BufferedReader br = null;
try {
// 1. 创建字节流(FileInputStream)
FileInputStream fis = new FileInputStream("test.txt");
// 2. 字节流转字符流,指定编码格式(UTF-8),解决乱码
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
// 3. 包装成缓冲字符流,提升效率
br = new BufferedReader(isr);
// 读取文本
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
补充:OutputStreamWriter用法类似,用于将字符流转换为字节流,指定写入编码,避免中文乱码。
五、JDK7+新特性:try-with-resources(自动关闭流,简化代码)
在JDK7之前,我们必须在finally块中手动关闭IO流,代码繁琐且容易遗漏,导致资源泄漏。JDK7引入了try\-with\-resources语法,可自动关闭实现了AutoCloseable接口的资源(所有IO流类都实现了该接口),简化代码,彻底避免资源泄漏。
5.1 核心原理
将需要关闭的资源(IO流)声明在try关键字的括号中,当try块执行结束后(无论是否出现异常),JVM会自动调用资源的close\(\)方法,无需手动在finally中关闭。
5.2 实战示例(简化缓冲流代码)
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TryWithResourcesDemo {
public static void main(String[] args) {
// try-with-resources:括号中声明需要关闭的资源,多个资源用分号分隔
try (// 节点流
FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("target.jpg");
// 缓冲流包装节点流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
// 复制数据
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.flush();
System.out.println("文件复制成功!");
} catch (IOException e) {
// 捕获所有IO异常
e.printStackTrace();
}
// 无需finally块,JVM自动关闭所有资源(顺序:先关闭bos、bis,再关闭fos、fis)
}
}
优势:代码简洁,无需手动关闭流,彻底避免资源泄漏;推荐在JDK7+环境中优先使用。
六、IO流实战综合案例(开发高频)
结合实际开发场景,整理3个高频实战案例,覆盖IO流的核心用法,代码可直接复用。
案例1:文本文件逐行读取并修改(缓冲字符流)
场景:读取一个文本文件,将文件中所有“Java”替换为“Java IO”,并写入新的文件中。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class TextModifyDemo {
public static void main(String[] args) {
// try-with-resources自动关闭流
try (BufferedReader br = new BufferedReader(new FileReader("source.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("target.txt"))) {
String line;
// 逐行读取
while ((line = br.readLine()) != null) {
// 替换字符串
String newLine = line.replace("Java", "Java IO");
// 写入新文件,换行
bw.write(newLine);
bw.newLine();
}
bw.flush();
System.out.println("文本修改完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例2:文件复制工具类(通用,支持所有文件类型)
场景:封装一个文件复制工具类,支持复制任意类型的文件(文本、图片、视频等),使用缓冲字节流提升效率。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* IO流文件复制工具类
*/
public class FileCopyUtil {
// 私有构造方法,禁止实例化
private FileCopyUtil() {}
/**
* 复制文件
* @param sourcePath 源文件路径
* @param targetPath 目标文件路径
* @throws IOException IO异常(文件不存在、权限不足等)
*/
public static void copyFile(String sourcePath, String targetPath) throws IOException {
// try-with-resources自动关闭流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourcePath));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetPath))) {
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,提升效率
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.flush();
}
}
// 测试
public static void main(String[] args) {
try {
// 复制图片
FileCopyUtil.copyFile("source.jpg", "target.jpg");
// 复制文本文件
FileCopyUtil.copyFile("source.txt", "target.txt");
System.out.println("所有文件复制完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例3:读取键盘输入(标准输入流)
场景:读取用户从键盘输入的内容,直到输入“exit”为止,将输入的内容写入文本文件。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.FileWriter;
public class KeyboardInputDemo {
public static void main(String[] args) {
// 标准输入流(键盘输入):System.in是字节流,转字符流处理
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new FileWriter("keyboard.txt", true))) {
System.out.println("请输入内容(输入exit退出):");
String line;
while (true) {
// 读取键盘输入的一行内容
line = br.readLine();
// 判断是否退出
if ("exit".equals(line)) {
break;
}
// 写入文件
bw.write(line);
bw.newLine();
}
bw.flush();
System.out.println("输入完成,内容已写入文件!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
七、IO流避坑指南(开发高频,必看)
IO流看似简单,但新手容易踩坑,导致资源泄漏、中文乱码、数据丢失等问题,以下是6个高频坑点,结合实例说明,帮你避开陷阱。
坑点1:忘记关闭流,导致资源泄漏
IO流是稀缺资源,使用后未关闭,会占用系统文件句柄、内存等资源,长期运行会导致程序崩溃。
坑点2:缓冲流未flush(),导致数据丢失
缓冲流(BufferedInputStream、BufferedWriter等)会将数据先存储在缓冲区中,只有当缓冲区满、调用flush()或close()时,才会将数据写入目标设备。若未调用flush(),数据可能残留缓冲区,导致数据丢失。
坑点3:字符流处理非文本数据,导致文件损坏
字符流只能处理文本数据(如.txt、.java),若用字符流读取/写入图片、视频、音频等二进制数据,会导致数据丢失、文件损坏。
坑点4:编码不统一,导致中文乱码
当读取和写入文本文件时,编码格式不统一(如文件是UTF-8,读取时用GBK),会出现中文乱码,需用InputStreamReader/OutputStreamWriter指定编码。
坑点5:流关闭顺序错误,导致资源泄漏
当使用多个流嵌套(如缓冲流包装节点流)时,关闭顺序错误会导致资源泄漏,正确顺序是「先关闭外层流,再关闭内层流」。
坑点6:使用read()方法判断读取结束时,忽略-1
InputStream和Reader的read()方法返回-1表示读取到末尾,若忽略-1,会将-1当作有效数据处理,导致数据错误。
八、面试高频:IO流相关面试题(附答案)
IO流是Java基础面试的高频考点,尤其是字节流与字符流的区别、缓冲流的作用、资源释放等,以下是5道最常考的面试题,附简洁答案(面试直接用)。
面试题1:Java IO流的核心分类有哪些?
答:按3个维度分类:① 按数据流向:输入流、输出流;② 按传输单位:字节流(InputStream/OutputStream)、字符流(Reader/Writer);③ 按功能:节点流(底层流)、处理流(包装流)。
面试题2:字节流和字符流的区别是什么?什么时候用字节流,什么时候用字符流?
答:区别:① 传输单位:字节流以字节为单位,字符流以字符为单位;② 适用场景:字节流适用于所有数据(文本、图片、视频),字符流只适用于文本数据;③ 编码支持:字符流支持编码转换,字节流不支持。
使用场景:处理文本数据用字符流(便捷、支持编码),处理非文本数据(图片、视频)用字节流(避免文件损坏)。
面试题3:缓冲流(BufferedInputStream/BufferedReader)的作用是什么?原理是什么?
答:作用:提升IO读写效率,减少磁盘IO次数。
原理:设置一块内存缓冲区,读取数据时,先将数据读取到缓冲区,再从缓冲区读取到程序;写入数据时,先将数据写入缓冲区,缓冲区满后再一次性写入目标设备,减少磁盘IO次数(内存读写比磁盘快)。
面试题4:IO流的资源为什么必须关闭?如何正确关闭IO流?
答:原因:IO流占用系统稀缺资源(文件句柄、内存等),不关闭会导致资源泄漏,长期运行会导致程序崩溃。
正确关闭方式:① JDK7之前:在finally块中手动关闭,先关闭外层流,再关闭内层流;② JDK7+:使用try-with-resources语法,JVM自动关闭流(推荐)。
面试题5:InputStreamReader和OutputStreamWriter的作用是什么?
答:作用:实现字节流和字符流的转换,同时支持指定字符编码(如UTF-8、GBK),解决中文乱码问题。InputStreamReader将字节输入流转换为字符输入流,OutputStreamWriter将字符输出流转换为字节输出流。
九、总结
Java IO流的核心是「数据传输通道」,掌握其分类(字节流/字符流、输入流/输出流、节点流/处理流)是基础,熟练使用常用流类(FileInputStream、BufferedReader、InputStreamReader等)是关键。
开发中需牢记3个核心原则:① 字节流处理所有数据,字符流只处理文本;② 缓冲流提升效率,必须flush()或close();③ IO流必须关闭,优先使用try-with-resources避免资源泄漏。
Java IO流全解析:从基础到实战,彻底吃透文件与数据操作 在Java开发中,IO(Input/Output)流是核心基础知识点之一,贯穿于文件操作、网络