IO的分类
- 基于字节操作的 I/O 接口:InputStream 和 OutputStream
- 基于字符操作的 I/O 接口:Writer 和 Reader
- 基于磁盘操作的 I/O 接口:File
- 基于网络操作的 I/O 接口:Socket
字节流和字符流是根据数据的传输格式而分类的,为解决字节流的部分乱码等问题为进一步开发的字符流,后两种则是更加传输数据的方式不同而划分的。
- 按作用对象划分
-
按功能划分
节点流的学习
文件
针对文件的输入
FileInputStream
该类可以作为输入流的一个例子,有两个需要在注意的点在于:
- 使用读取数组的方式时,声明的数组可以看作是一个缓冲区(仅仅只是看作,实际上只是一个临时存放数据的容器),不是要将数据一次性全部读到数组中,而是每次数组填满后,循环读取,下次循环是将数组的值覆盖,如果没有覆盖到,数据还是上次的数据内容。
- read(char arrays[], int offset, int length),该方法是指读取length个字符放置在缓冲区arrays的offset位置。
public class Test01 {
public static void main(String[] args) {
getFileInputStream();
getInputStreamReader();
getInputStreamReaderByteArray();
getInputStreamReaderByByteArray2() ;
}
private static final String FILE_NAME = "文件名称";
/**
* 字节流读取会带有中文字母,需要转换为字符流
*/
public static void getFileInputStream() {
// 获取输入字节流
try (InputStream in = new FileInputStream(FILE_NAME)) {
int read;
while ((read = in.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用了InputStreamReader 转换字节流为字符流
*/
public static void getInputStreamReader() {
try (InputStreamReader in = new InputStreamReader(new FileInputStream(FILE_NAME))) {
int read;
while ((read = in.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用InputStreamReader读取字符数组
*/
public static void getInputStreamReaderByteArray() {
try (InputStreamReader in = new InputStreamReader(new FileInputStream(FILE_NAME))) {
char[] chars = new char[50];
int count;
while ((count = in.read(chars)) != -1) {
System.out.print(count);
System.out.print(new String(chars, 0, count));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 关于read(char cbuf[], int offset, int length)方法的解读:
* 读取length个字符,放置在cbuf数组的offset位置
*/
public static void getInputStreamReaderByByteArray2(){
try(InputStreamReader in = new InputStreamReader(new FileInputStream(FILE_NAME))){
char[] chars = new char[50];
int count;
while ((count = in.read(chars,1,2)) != -1){
System.out.print(new String(chars,1,count));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileOutputStream
输出字节流到文件中,需要有以下需要注意的点:
- 它没有实现flush方法,该方法用作将缓存区的数据写入目标,这意味着它没有缓存区,发送数据的数组只看作临时存放数据的地点
- 在构造器中添加append,可以决定是否追加数据到目标文件
public class Test02 {
public static void main(String[] args) {
getOutPutStream();
getOutPutStreamByByteArrays();
getOutPutStreamByByteArrays2();
}
private static final String FILE_NAME = "文件地址";
/**
* 使用输出流
*/
public static void getOutPutStream(){
// FileOutPutStream的构造方法中有参数为append,当为true时,即为给
try(OutputStream out = new FileOutputStream(FILE_NAME,true);) {
out.write('a');
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用数组输出字节流
*/
public static void getOutPutStreamByByteArrays(){
try(OutputStream out = new FileOutputStream(FILE_NAME)) {
byte[] bytes = "hello world".getBytes(StandardCharsets.UTF_8);
out.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* write(byte b[], int off, int len) 等于将从数组的off位置开始读取len个字节发送
*/
public static void getOutPutStreamByByteArrays2(){
try(OutputStream out = new FileOutputStream(FILE_NAME)) {
byte[] bytes = "hello world".getBytes(StandardCharsets.UTF_8);
out.write(bytes,2,5);
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader
字符流的方式读取文件数据,FilReader继承InputStreamReader,发现InputStreamReader是字节流转字符流的关键,其中的读取文件的操作与FileInputStream的写法基本一致
public class Test03 {
public static void main(String[] args) {
getFileReader();
getFileReaderByCharArrays();
getFileReaderByCharArrays2();
}
/**
* 单个字节读取
*/
public static void getFileReader() {
try (Reader reader = new FileReader(MyConstanst.FILE_NAME)) {
int read;
while ((read = reader.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用字符数组的方式接收字符流
*/
public static void getFileReaderByCharArrays() {
try (FileReader reader = new FileReader(MyConstanst.FILE_NAME)) {
char[] bytes = new char[1024];
int count;
while ((count = reader.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, count));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用字符数组
*/
public static void getFileReaderByCharArrays2() {
try (FileReader reader = new FileReader(MyConstanst.FILE_NAME)) {
char[] chars = new char[1024];
int count;
while ((count = reader.read(chars, 0, 3)) != -1) {
System.out.print(new String(chars, 0, count));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileWriter
FileWriter的是OutputSteam的子类,是Writer的子孙类,通过查看源码,能发现FileWriter的实际上调用的OutputSteam的方法,并使用了StreamEncoder进行转换。
public class Test04 {
public static void main(String[] args) {
getFileWriter();
}
public static void getFileWriter(){
try(Writer out = new FileWriter(MyConstanst.FILE_NAME)) {
out.write("hello");
} catch (IOException e) {
e.printStackTrace();
}
}
}
数组
ByteArrayInputStream
当数据源就是字节数组的时候,即可以使用ByteArrayInputStream直接获取字节流,省去了其他转换的过程,方便做后续的其他操作
public class Test05 {
public static void main(String[] args) {
getByteArrayInputStream();
}
/**
* 当数据源就是字节数组的时候,即可以使用ByteArrayInputStream获取字节流,方便做后续的其他操作
*/
public static void getByteArrayInputStream() {
byte[] bytes = "hello java".getBytes(StandardCharsets.UTF_8);
try (InputStream inputStream = new ByteArrayInputStream(bytes);) {
int read;
while ((read = inputStream.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ByteArrayOutputStream
字节数组的输出流,在ByteArrayOutputStream中创建了一个字节数组,当读取数据到输出流的时候,会写进着字节数组中。通过toByteArray()方法读取出来。注意向上转型的问题,向上转型会丢失子类自身的属性和方法,例如:OutputStream out = new ByteArrayOutputStream(64),这是就调用不了子类自身的toByteArray()方法。
public class Test06 {
public static void main(String[] args) {
getByteArrayOutputStream();
}
public static void getByteArrayOutputStream() {
// 创建字节数组
byte[] bytes = "Shh".getBytes(StandardCharsets.UTF_8);
// 将字节数组转换为输出流
try (ByteArrayOutputStream out = new ByteArrayOutputStream(64)) {
// ByteArrayOutputStream内部创建了一个字节数组,会把数据存放在那里,必要时扩容,默认大小为32
out.write(bytes);
// 从流中读取出字节数组,也可以转换为输入流,再读取出来,对数据进行处理
byte[] target = out.toByteArray();
ByteArrayInputStream in = new ByteArrayInputStream(target);
int read;
while ((read = in.read()) != -1) {
// 相关逻辑处理
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
CharArrayReader
Reader的实现类,可以对字符数组进行更方便的操作
public class Test07 {
public static void main(String[] args) {
getCharArrayReader();
}
public static void getCharArrayReader() {
// 源数组
char[] source = {'h', 'h', 'h', 'h', 'h', 'h'};
// 将源数组读取到输入流中
try (CharArrayReader reader = new CharArrayReader(source)) {
int read;
while ((read = reader.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
CharArrayWriter
writer的实现类
public class Test08 {
public static void main(String[] args) {
getCharArrayWriter();
}
public static void getCharArrayWriter() {
char[] source = {'h', 'e'};
try (CharArrayWriter out = new CharArrayWriter(64)) {
out.write(source);
char[] to = out.toCharArray();
CharArrayReader in = new CharArrayReader(to);
int count;
char[] too = new char[16];
while ((count = in.read(too)) != -1) {
// 业务处理
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
管道操作
PipedInputStream、PipedOutputStream
该类用于线程间通信,创建读写两个线程,建立两个线程的连接,就可以在管道中进行数据的传输
public class Test09 {
public static void main(String[] args) throws IOException {
getPipedStream();
}
public static void getPipedStream() throws IOException {
// 官方注释说明这个适用于线程间通信,创建读写两个线程,建立管道连接
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);
// 写线程,将数据写入到管道中
Thread threadWriter = new Thread(() -> {
byte[] bytes = "hello piped".getBytes(StandardCharsets.UTF_8);
try {
out.write(bytes);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
});
// 读线程,从管道中读取数据
Thread threadRead = new Thread(() -> {
try {
int read;
while ((read = in.read()) != -1) {
System.out.print((char) read);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
});
threadWriter.start();
threadRead.start();
}
}
PipedReader、PipedWriter
public class Test10 {
public static void main(String[] args) throws IOException {
getPiped();
}
public static void getPiped() throws IOException {
PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
reader.connect(writer);
Thread threadWriter = new Thread(() -> {
try {
char[] chars = {'s', 'b'};
writer.write(chars);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
});
Thread threadReader = new Thread(() -> {
try {
int read;
while ((read = reader.read()) != -1) {
System.out.print((char) read);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
});
threadWriter.start();
threadReader.start();
}
}
处理流的学习
转换
InputStreamReader
InputStreamReader是从字节流到字符流的桥:它读取字节,并使用指定的charset
将其解码为字符 。它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。每个调用InputStreamReader的read()方法之一可能会导致从底层字节输入流读取一个或多个字节。 为了使字节有效地转换为字符,可以从底层流读取比满足当前读取操作所需的更多字节。
public class Test11 {
public static void main(String[] args) {
byte[] bytes = "缘起缘灭".getBytes(StandardCharsets.UTF_8);
try (InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(bytes))) {
int read;
while ((read = in.read()) != -1) {
System.out.print((char) read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStreamWriter
OutputStreamWriter将字符转换成字节,通过构造方法指定字符编码集,以下例子中,如果编码为uft-8,则中文字符能正常输出,若为gbk则会乱码
public class Test12 {
public static void main(String[] args) {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream(64);
OutputStreamWriter out = new OutputStreamWriter(stream, "utf-8")) {
out.write("缘起缘灭");
// OutputStreamWriter重写了flush方法,代码测试得知如果不调用此方法,则获取不到信息
out.flush();
// 从输出流中获取字节数组
byte[] bytes = stream.toByteArray();
System.out.print(new String(bytes, 0, bytes.length));
} catch (IOException e) {
e.printStackTrace();
}
}
}
序列化和反序列化
ObjectOutputStream、ObjectInputStream
Java提供了序列化的接口Serializable,只要类实现了该类,即可实现对象的序列化,有几点需要注意的是:
- 实现Serializable
- 创建serialVersionUID
- 对于不想序列化的属性,添加关键字transient,在反序列化时,该属性为null
ObjectOutputStream和ObjectInputStream为序列化提供了实现,序列化的作用:
- 对象持久化
- 网络传输
- 跨平台数据交换
- 缓存对象
Student类:
public class Student implements Serializable {
private final static long serialVersionUID = -5718005322642298180L;
private String name;
private int age;
private String account;
/**
* 密码比较私密,添加transient关键字,不会将该字段进行序列化
*/
private transient String password;
/**
* 构造器
*
* @param name
* @param age
* @param account
* @param password
*/
public Student(String name, int age, String account, String password) {
this.name = name;
this.age = age;
this.account = account;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", account='" + account + '\'' +
", password='" + password + '\'' +
'}';
}
}
进行序列化和反序列化:
这里是将对象保存在了文件中,也可以将对象放在数组中,有很多操作。
public class Test13 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化
getSerialization();
// 反序列化
getDeserialization();
}
/**
* 序列化
*/
public static void getSerialization() throws IOException {
// 创建序列化对象
Student student = new Student("张三", 18, "jame", "123456");
// 将序列化对象保存在文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(MyConstanst.FILE_NAME));
out.writeObject(student);
out.flush();
out.close();
}
/**
* 反序列化
* @throws IOException
* @throws ClassNotFoundException
*/
public static void getDeserialization() throws IOException, ClassNotFoundException {
// 创建反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(MyConstanst.FILE_NAME));
// 反序列化对象
Student student = (Student) in.readObject();
in.close();
System.out.println(student.toString());
}
}
打印
PrintStream、PrintWriter
PrintStream 和 PrintWriter 都是 Java 中用于输出数据的打印流类
public class Test14 {
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
// getPrintStream();
getPrintWriter();
}
public static void getPrintStream() {
try {
// PrintStream printStream = new PrintStream(MyConstanst.FILE_NAME,"utf-8");
PrintStream printStream = new PrintStream(new FileOutputStream(MyConstanst.FILE_NAME, true));
printStream.write('a');
printStream.println(99);
printStream.print(11L);
// %s占位符
printStream.printf("%s喜欢%s", "小明", "小红");
printStream.flush();
printStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void getPrintWriter() {
try {
PrintWriter printWriter = new PrintWriter(MyConstanst.FILE_NAME, "UTF-8");
printWriter.write('e');
printWriter.println(22);
// %s占位符
printWriter.printf("%s喜欢%s", "小明", "小红");
printWriter.flush();
printWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
缓冲操作
BufferedInputStream、BufferedOutputStream
- A
BufferedInputStream
为另一个输入流添加了功能,即缓冲输入和支持mark
和reset
方法的功能。 当创建BufferedInputStream
时,将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。mark
操作会记住输入流中的一点,并且reset
操作会导致从最近的mark
操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。 - BufferedOutputStream该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用。
public class Test15 {
public static void main(String[] args) {
getBufferedOutputStream();
getBufferedInputStream();
}
/**
* 给输入流添加了一个缓存区,效率更高
*/
public static void getBufferedInputStream() {
try (FileInputStream fi = new FileInputStream(MyConstanst.FILE_NAME);
BufferedInputStream bi = new BufferedInputStream(fi)) {
int count;
byte[] bytes = new byte[1024];
while ((count = bi.read(bytes)) != -1) {
System.out.print(new String(bytes, 0, count));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 给输出流添加了一个缓存区,效率更高
*/
public static void getBufferedOutputStream() {
try (FileOutputStream fo = new FileOutputStream(MyConstanst.FILE_NAME, true);
BufferedOutputStream bo = new BufferedOutputStream(fo)) {
bo.write("hello world".getBytes(StandardCharsets.UTF_8));
bo.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedReader、BufferedWriter
与bufferedInputStream不同的是:bufferedReader可以读取整行的数据,写法与前面的略有不同,但是效率更高了
public class Test16 {
public static void main(String[] args) {
getBufferedWriter();
getBufferedReader();
}
public static void getBufferedReader() {
try (FileReader fr = new FileReader(MyConstanst.FILE_NAME);
BufferedReader br = new BufferedReader(fr)) {
String line = br.readLine();
while (null != line) {
System.out.println(line);
line = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void getBufferedWriter() {
try (FileWriter fw = new FileWriter(MyConstanst.FILE_NAME);
BufferedWriter bw = new BufferedWriter(fw)) {
// 空一行
bw.newLine();
bw.write("为什么");
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
基本数据类型
DataInputStream、DataOutputStream
这两个类有装饰了很多基本的数据类型,有以下几点需要注意:
- 当使用DataOutputStream写入多个不同的数据类型时,在使用DataInputStream读取数据的时候要保证顺序要与写入的顺序相同
- writeUTF()写入的数据是修改版的数据,与自己写到文档的数据不同,即自己写入的数据用readUTF()读取会异常。同样的,其他基本类型也是相同。使用其他流往文本里写的其实是byte数组。如果要用DataInputStream读取的时候也只能用byte数组的方式读取。
public class Test17 {
public static void main(String[] args) {
// getDataOutputStream();
getDataInputStream();
}
/**
* 对输入流进行了包装,读取数据类型的顺序要和写入的顺序保持一致,否则会报错
*/
public static void getDataInputStream() {
try (FileInputStream fi = new FileInputStream(MyConstanst.FILE_NAME);
DataInputStream di = new DataInputStream(fi)) {
// 读取的顺序要保持一致,大部分时间只会有一种数据类型
// System.out.println(di.readBoolean());
// System.out.println(di.readInt());
// System.out.println(di.readUTF());
int count;
while ((count = di.read()) != -1) {
System.out.print((char) count);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 对输出流进行了包装,可以写很多类型的数据
*/
public static void getDataOutputStream() {
try (FileOutputStream fo = new FileOutputStream(MyConstanst.FILE_NAME);
DataOutputStream dox = new DataOutputStream(fo)) {
dox.writeBoolean(false);
dox.writeInt(99);
dox.writeUTF("hello world");
dox.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
压缩
ZipOutStream
本类用以对文件进行zip压缩操作,重点注意以下:
- 使用递归的方式对文件夹下的所有文件进行读取
- ZipEntry对应着一个文件,在它的构造函数中传递的文件路径,即是在压缩包中的文件路径,所以需要考虑文件路径怎么获取能还原在原本文件夹中的路径顺序
- 使用ByteArrayOutputStream来临时存储文件信息,效率是否合理
学习示例:
该示例用作学习,从压缩单个文件,到多个文件的过程
public class Test18 {
public static void main(String[] args) {
}
/**
* 压缩了单个文件
*/
public static void zip() {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(MyConstanst.ZIP_NAME))) {
// 创建一个实体,关联输出流
ZipEntry zipEntry = new ZipEntry("index/file.txt");
zipOutputStream.putNextEntry(zipEntry);
// 往实体中写入数据
zipOutputStream.write("缘起缘灭".getBytes(StandardCharsets.UTF_8));
zipOutputStream.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 压缩多个文件,该方法只是学习使用,与实际应用场景不同
*/
public static void zipAll() {
List<String> list = Arrays.asList("first/file.txt", "first/second/file2.txt", "first/second/third/file3.txt");
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(MyConstanst.ZIP_NAME))) {
for (String filePath : list) {
ZipEntry zipEntry = new ZipEntry(filePath);
zipOut.putNextEntry(zipEntry);
zipOut.write("what".getBytes(StandardCharsets.UTF_8));
zipOut.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 升级版,但是肯定是需要自动获取到文件夹下的所有文件,而不是手动填写
*/
public static void zipAllAgain() {
String fileAdd = "C:/Users/songj/Desktop/my/";
List<String> list = Arrays.asList("first/file.txt", "first/second/file2.txt", "first/second/third/file3.txt");
try (
// 创建一个数组的字节流,准备接收各个文件的数据
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ZipOutputStream zo = new ZipOutputStream(bo);
) {
for (String filePath : list) {
FileInputStream fis = new FileInputStream(fileAdd + filePath);
ZipEntry zipEntry = new ZipEntry(filePath);
zo.putNextEntry(zipEntry);
int count;
byte[] bytes = new byte[1024];
while ((count = fis.read(bytes)) != -1) {
zo.write(bytes, 0, count);
}
fis.close();
zo.closeEntry();
}
zo.close();
byte[] bytes = bo.toByteArray();
FileOutputStream fos = new FileOutputStream(MyConstanst.ZIP_NAME);
fos.write(bytes);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
可用的版本:
使用了ByteArrayOutputStream来存储各个文件的数据,最终输出文件
public class Test18 {
public static void main(String[] args) {
zipFinal();
}
public static void zipFinal() {
// 源文件夹或者源文件
String source = "C:/Users/songj/Desktop/my";
// 要输出压缩文件的路径
String dit = "C:/Users/songj/Desktop/my.zip";
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(64);
ZipOutputStream zos = new ZipOutputStream(bos)) {
File file = new File(source);
// 递归的方式获取文件,并写入到zos中
getAllFileByRecursion(file, zos, file.getName());
zos.close();
byte[] bytes = bos.toByteArray();
FileOutputStream fos = new FileOutputStream(dit);
fos.write(bytes);
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通过递归的方式获取文件夹下的所有文件名称
*
* @param file
* @param zos
*/
public static void getAllFileByRecursion(File file, ZipOutputStream zos, String parentFoldName) {
if (file.isDirectory()) {
for (File listFile : file.listFiles()) {
String filePath = parentFoldName + File.separator + listFile.getName();
getAllFileByRecursion(listFile, zos, filePath);
}
} else {
try {
System.out.println("输出到压缩包内的地址:" + parentFoldName);
ZipEntry zipEntry = new ZipEntry(parentFoldName);
zos.putNextEntry(zipEntry);
FileInputStream fis = new FileInputStream(file.getPath());
int count;
byte[] bytes = new byte[1024];
while ((count = fis.read(bytes)) != -1) {
zos.write(bytes, 0, count);
}
fis.close();
zos.closeEntry();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ZipIntputStream
本类用以对zip压缩的文件进行解压等操作
public class Test20 {
public static void main(String[] args) {
getZipInputStream();
}
public static void getZipInputStream() {
String dit = "目标文件夹";
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(MyConstanst.ZIP_NAME))) {
ZipEntry nextEntry = zis.getNextEntry();
while (nextEntry != null) {
String ditPath = dit + File.separator + nextEntry.getName();
System.out.println("解压后的资源路径:" + ditPath);
File file = new File(ditPath);
if (!nextEntry.isDirectory()) {
// 创建父文件夹
file.getParentFile().mkdirs();
// 输出文件
FileOutputStream fos = new FileOutputStream(ditPath);
int count;
byte[] bytes = new byte[1024];
while ((count = zis.read(bytes)) != -1) {
fos.write(bytes, 0, count);
}
fos.close();
// 关闭目录资源
zis.closeEntry();
} else {
// 当前为文件夹
file.mkdirs();
}
nextEntry = zis.getNextEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
网络编程
TCP
socket是网络通信的根本,针对学习的结果做一些总结和整理,还有很多需要学习的,写代码也更加需要思考和总结:
- 不论是创建socket对象,还是serversocket接受的socket对象,都能获取到输入流和输出流,可以使用字节流,也可以使用字符流。
- 当需要使用输入流获取对端发送的数据时,是无法获取到流结束的状态(字节流的-1或者字符流的null),除非对端调用shutdownOutput()关闭对端自己的输出流,或者对端关闭了socket连接。但如果对端调用了shutdownOutput()方法,那么它就不能再使用输出流了。当使用while循环来读取socket中获取的输入流,由于不能获取到结束状态,就会一直阻塞(字节流和字符流都是不能获取到状态的)。而不使用while循环的话只能读取一部分数据(一个字节数组或者一行数据),这需要看业务自行设计。
- 使用reaLine()读取一行数据的前提是对端只发送一行数据。BufferedReader的readLine()方法和PrintWriter的println()方法。readLine()会在读取到\n时结束阻塞,而println()会在发送的数据结尾加上\n。(实际上readline()也是没有读完数据的,只是读取了一行数据,而这个是两端约定好的。
- ServerSocket可以接收多个socket客户端的连接,这需要使用的多线程。
整理一下解决循环读取的方法:
- 对端调用shutdownOutput()方法,这适用与只进行单次通信的情况。
- 在接受到的数据头部说明数据的长度,或者约定数据帧的起止标识。这样由本端自行判断数据的结束情况。
- 给socket设置超时时间。不建议使用
方法 | 说明 |
---|---|
shutdownOutput() | 关闭本端输出流,后续不再使用,如果使用将会报错 |
isOutputShutdown() | 获取本端输出流是否被关闭,已被关闭返回true |
shutdownInput() | 关闭本端输入流,不再接受对端发送的数据,读取时都会返回-1 |
isInputShutdown | 获取本端输入流是否被关闭,已被关闭返回true |
客户端代码示例:
public class TestSocket03 {
public static void main(String[] args) {
socketClient();
}
public static void socketClient() {
try (Socket socket = new Socket("127.0.0.1", 8998);
BufferedReader bf = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true)) {
// 键盘输入
System.out.println("请从键盘输入发送给服务端的信息,若退出请输入[exit]");
Scanner scanner = new Scanner(System.in);
String input = new String();
while (!input.equals("exit")) {
input = scanner.nextLine();
pw.println(input);
// 每次发送完,就读取服务端发送的消息
System.out.println(bf.readLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码示例:
public class TestSocket02 {
public static void main(String[] args) {
socketServer();
}
/**
* 服务端
*/
public static void socketServer() {
try {
ServerSocket server = new ServerSocket(8998);
// server.setSoTimeout(3000);
System.out.println("服务端已启动,等待客户端连接");
while (true) {
// accept()会一直阻塞,知道与客户端建立了连接
Socket accept = server.accept();
System.out.println("已建立连接");
// 将连接交给新的线程处理
MySSThread thread = new MySSThread(accept);
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 线程信息
**/
public class MySSThread extends Thread {
private Socket socket;
public MySSThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 随机生成连接号
int i = new Random().nextInt(5000);
System.out.println("用户" + i + "已建立连接");
// 简化输入流
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
// 简化输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg;
// BufferedReader的readline()方法读到\n或null就能结束,不会阻塞。而PrintWriter的println()会在写入消息后添加\n。可以搭配着使用
while ((msg = br.readLine()) != null) {
System.out.println("来自客户端"+i+"的消息:" + msg);
// 可以做些业务处理,再返回给客户端,这里的demo就直接原样返回消息了,待完善
pw.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
UDP
核心的两个类:
- DatagramSocket:创建客户端和服务端都是这个,不同的是服务端需要监听指定端口
- DatagramPacket:数据包,发送数据和接收数据使用同一个,发送数据需要绑定端口和地址,接收数据需要创建一个数组临时存放,在接受的DatagramPacket中,除了数据外,还有它来源的端口和地址,为后续发送数据回去提供了依据。
服务端示例:
public class Test05 {
public static void main(String[] args) {
server();
}
public static void server() {
try {
// 端口
int port = 7777;
// 为接收数据创建的临时空间
byte[] receiverData = new byte[1024];
// 准备发送数据
byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8);
// 创建服务端对象
DatagramSocket ds = new DatagramSocket(port);
System.out.println("服务端启动,开发收发数据");
while (true){
// 接收数据
DatagramPacket receiveDP = new DatagramPacket(receiverData, receiverData.length);
// 阻塞读取
ds.receive(receiveDP);
String receiverMsg = new String(receiverData, 0, receiveDP.getLength());
System.out.println(receiverMsg);
// 返回的receiveDP包含了接收到的地址和端口
InetAddress ipAddress = receiveDP.getAddress();
port = receiveDP.getPort();
// 发送数据
DatagramPacket sendDP = new DatagramPacket(bytes,bytes.length,ipAddress,port);
ds.send(sendDP);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端示例:
public class TestSocket04 {
public static void main(String[] args) {
client();
}
public static void client() {
try {
// 与服务的区别是没有指定端口
DatagramSocket datagramSocket = new DatagramSocket();
// 准备数据
byte[] bytes = "hello host by udp".getBytes(StandardCharsets.UTF_8);
InetAddress ipAddress = InetAddress.getByName("localhost");
DatagramPacket sendDP = new DatagramPacket(bytes, bytes.length, ipAddress, 7777);
// 发送数据
datagramSocket.send(sendDP);
// 接收数据
byte[] receiveData = new byte[1024];
DatagramPacket receiveDP = new DatagramPacket(receiveData, receiveData.length);
datagramSocket.receive(receiveDP);
String msg = new String(receiveData, 0, receiveDP.getLength());
System.out.println("接受的到的消息:" + msg);
datagramSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}