IO流与文件传输
一、Socket常用的IO流
前面的实验完成了TCP和UDP的基本通信。在TCP通信里我们用到了这样两句代码:
//通过BufferedReader获取socket套接字的输入流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//通过PrintWriter获取客socket套接字的输出流
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
在这里就涉及到了Java IO流的概念。Java的IO流包括字符流(基类为Reader和Writer)和字节流(基类为InputStream和OutputStream),其具体实现类如图:

可以看出上面代码里用到的输入流BufferedReader和输出流PrintWriter均为字符流。在Socket通信中大多都是使用这两个类进行处理的。其中PrintWriter没有自己对应的输入流,而BufferedReader有对应的输出流叫BufferedWriter。那为什么输出流不用BufferedWriter呢?可以参考这个链接:https://blog.csdn.net/qq_38977097/article/details/80967896
简单的说就是:PrintWriter的参数既可以是字符流也可以是字节流,BufferedWriter只能是字符流;PrintWriter还能自动换行+自动刷新缓存(autoflush)。
类似的是BufferedReader的参数也只能是字符流。而Socket.getInputStream()方法返回的是InputStream字节流,因此就需要转换流对其进行转换——
InputStreamReader(InputStream in);
由上面的图片中可以看到InputStreamReader本身就是字符流,因此它可以作为BufferedReader的参数,它的构造函数可以传入一个字节流,而它自己则起到一个中介作用。这样就解释了上面那两句代码的由来。
二、字节流与字符流
接下来要用IO流+Socket进行文件的传输。由于是文件传输(高精度+低速度)首选TCP。现在的问题是:选择用字节流还是字符流?
字节流:主要操作 byte 类型数据,以 byte 数组为准,java 中每一种字节流的基本功能依赖于基本类 InputStream 和 Outputstream。字节流能处理所有类型的数据(如图片、avi等)。
字符流:以字符为单位,根据码表映射字符,一次可能读多个字节,java 中每一种字符流的基本功能依赖于基本类Reader 和Writer。字符流只能处理字符类型的数据。

二者的区别可参考:https://blog.csdn.net/cynhafa/article/details/6882061
简单的说:传文本文件选择字符流,传图片等选择字节流。
三、Socket传输文本文件
使用字符流进行文件的读写可以使用FileReader和FileWriter,但更好的方法是使用带缓冲的BufferedReader和BufferedWriter(PrintWriter)。缓冲流一方面可以提高读写的效率,一方面也支持自动换行,极大方便了文本文件的读写。
PrintWriter写文件可以直接在构造函数里传入文件的路径,如果文件不存在则会创建文件:
PrintWriter fileOut = new PrintWriter("statics/file.txt");
fileOut.println("Hello World!"); //在文件里写入数据,可自动换行
fileOut.close(); //字符流一定要close
而BufferedReader在读文件时有两种选择。第一种是使用FileReader打开文件并转化成字符流传入构造函数:
BufferedReader fileIn = new BufferedReader(new FileReader("D:/hello.txt"));
但这种方法的问题在于不支持读入中文文本。这是因为FileReader默认使用当前操作系统的编码方式,即中文版Windows下使用的GBK,而Java在解析时使用Unicode编码,二者不统一导致中文的乱码。所以读文件时应该使用另外一种办法:
BufferedReader fileIn = new BufferedReader(new InputStreamReader(new FileInputStream("D:/hello.txt"), Charset.forName("UTF-8")));
即用字节流打开文件并转化成字符流。这样做的好处是InputStreamReader的第二个参数可以指定编码规则为UTF-8,这样在读中文字符时就不会有乱码了。
下面是代码:
- TcpFileServer.java
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 使用字符流传输文本文件
* 接收端程序
*/
public class TcpFileServer
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket = new ServerSocket(1081);
Socket socket = serverSocket.accept();
//接收socket输入流
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//使用PrintWriter写文件
PrintWriter fileOut = new PrintWriter("statics/file.txt");
String s;
while((s = in.readLine())!=null)
{
fileOut.println(s);
}
System.out.println("文件传输成功");
fileOut.close();
serverSocket.close();
socket.close();
}
}
- TcpFileClient.java
import java.io.*;
import java.net.Socket;
import java.nio.charset.Charset;
/**
* 使用字符流传输文本文件
* 发送端程序
*/
public class TcpFileClient
{
public static void main(String[] args) throws Exception
{
String hostname = "localhost";//192.168.131.131
int port = 1081;
Socket socket = new Socket(hostname,port);
//使用BufferedReader读文件
//BufferedReader fileIn = new BufferedReader(new FileReader("D:/hello.txt"));//不支持中文
BufferedReader fileIn = new BufferedReader(new InputStreamReader(new FileInputStream("D:/hello.txt"), Charset.forName("UTF-8")));
//写入socket输出流
PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
String s;
while((s = fileIn.readLine())!=null)
{
out.println(s);
}
fileIn.close();
socket.close();
}
}
四、Socket传输图片
传图片、音频、视频等文件需要用到最基本的字节流。字节流在操作时不会用到缓冲区(内存),是对文件本身直接操作的,效率较高。
字节流需要用到FileInputStream和FileOutputStream类。
FileInputStream fileIn = new FileInputStream("D:/udp.png");
byte[] bytes = new byte[1024];
int len = fileIn.read(bytes);//read方法参数传入字节数组,每次最多可读1024(数组长度)个字节,返回已读的字节数
...
FileOutputStream fileOut = new FileOutputStream("statics/保存图片.png");
fileOut.write(bytes, 0, len);//write方法可将读入的字节数组写入文件中,0表示从下标0开始,len表示写入字节的个数
同时,由于Socket就是用字节流传输,因此可以直接用基类接收套接字的流。
InputStream in = socket.getInputStream(); //read方法和上面一样
...
OutputStream out = socket.getOutputStream(); //write方法和上面也一样
下面是代码:
- TcpImgServer.java
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 用字节流传输图片文件
* 接收端程序
*/
public class TcpImgServer
{
public static void main(String[] args) throws Exception
{
ServerSocket serverSocket = new ServerSocket(1081);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
FileOutputStream fileOut = new FileOutputStream("statics/保存图片.png");
byte[] bytes = new byte[1024];
int len;
while((len = in.read(bytes))!=-1){
System.out.println(len);
fileOut.write(bytes, 0, len);
}
fileOut.close();
socket.close();
serverSocket.close();
}
}
- TcpImgClient.java
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* 用字节流传输图片文件
* 发送端程序
*/
public class TcpImgClient
{
public static void main(String[] args) throws Exception
{
String hostname = "127.0.0.1";
int port = 1081;
Socket socket = new Socket(hostname,port);
FileInputStream fileIn = new FileInputStream("D:/udp.png");
OutputStream out = socket.getOutputStream();
byte[] bytes = new byte[1024];
int len;
while((len = fileIn.read(bytes))!= -1){
System.out.println(len);
out.write(bytes, 0, len);
}
fileIn.close();
socket.close();
System.out.println("发送成功!");
}
}

本文详细介绍了Java中Socket通信的IO流概念,对比了字符流与字节流的区别,以及如何使用它们进行文本文件和图片文件的传输。通过具体的代码示例,展示了使用BufferedReader、PrintWriter、FileInputStream和FileOutputStream等类进行文件读写的方法。

349

被折叠的 条评论
为什么被折叠?



