实验六 Java流式编程与网络程序设计

第1关:字节输入/输出流实现数据的保存和读取

package step1;

import java.io.\*;
import java.util.\*;

public class SortArray {

public static void main(String[] args) {
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
// 创建保存整型数据的数组(数组大小10)

byte[] data=new byte[10];
// 给数组赋随
Scanner sc = new Scanner(System._in_);
for (int i = 0; i \< data.length; i++) {
 data[i] = sc.nextByte();
 }
// 将数组元素按有小到大顺序排列
Arrays._sort_(data);

try {
// 创建数据保存文件,如果文件不存在,重新创建
File file = new File("data.txt");
if (!file.exists()) {
 file.createNewFile();
 }
// 创建FileOutputStream和DataOutputStream 输出流
FileOutputStream fileout = new FileOutputStream(file);
 DataOutputStream dataout = new DataOutputStream(fileout);
// 利用输出流向文件中写入数组数据

fileout.write(data,0,data.length);

// 关闭输出流
fileout.flush();
 fileout.close();
 dataout.close();

// 创建FileInputStream和DataInputStream 输入流
FileInputStream fileinput = new FileInputStream(file);


// 利用输入流从文件读取数据并输出
int n=0;

while((n=fileinput.read(data))!=-1){
for (int i = 0; i \< data.length; i++) {
if(i!=data.length-1){
 System._out_.print(data[i]+"\<");
 } else if (i==data.length-1) {
 System._out_.print(data[i]);
 }

 }

 }


// 关闭输入流
fileinput.close();

 } catch (IOException e) {
// 异常处理
System._out_.println("读写发生异常");
 }
_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_
}
 }

第2关:字符输入/输出流实现发送电报

package step2;

import java.io.\*;
import java.util.Scanner;

public class Encrypt {
public static void main(String[] args) throws IOException {

// 创建要发送的电报
Scanner sc = new Scanner(System._in_);
 String data = sc.next();

// 将电报分割成字符数组
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
char[] array=data.toCharArray();

_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_

// 打开指定存放电报的文件,如果文件不存在,则创建
File file = new File("data.txt");
if(!file.exists()) {
 file.createNewFile();
 }

// 循环遍历字符数组,将每个字符加密处理
for (int i = 0; i \< array.length; i++) {
 array[i] = (char) (array[i] ^ 'q');
 }

// 利用字符输出流FileWriter将加密后的字符数组写入文件中
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
FileWriter out=new FileWriter(file);
 System._out_.println("密文:");
 out.write(array,0,array.length);
 out.flush();
 out.close();

_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_

// 利用字符输入流FileReader读取文件,将密文输出
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
int n;
 FileReader in =new FileReader(file);
char[] buf=new char[10];
while((n=in.read(buf))!=-1){
 String s=new String(buf);
 System._out_.println(s);
 }

_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_


// 利用字符输入流FileReader读取文件,将密文转换为明文输出
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
FileReader in2=new FileReader(file);
 System._out_.println("明文:");
while((n=in2.read(buf))!=-1){
for(int i=0;i\<n;i++){
 buf[i]=(char)(buf[i] ^ 'q');
 }
 String s=new String(buf);
 System._out_.print(s);
 }
 in2.close();

_/\*\*\*\*\*\*\*\*\*\* End \*\*\*\*\*\*\*\*\*\*/_


}
 }

第3关:简单TCP通信

Client

package step3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

public class Client {

public static void main(String[] args) throws Exception {
 Server server = new Server();
 server.start();
 Scanner sc = new Scanner(System._in_);

//创建客户端Socket(s),指定服务器端IP地址和端口号
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
Socket s=new Socket("127.0.0.1",8000);

_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
 DataInputStream dis = new DataInputStream(s.getInputStream());
 System._out_.println(dis.readUTF());
 String name = sc.next();
 dos.writeUTF(name);
 System._out_.println(dis.readUTF());
 s.close();
 }
 }

Server

package step3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server extends Thread {

@Override
public void run() {
try {
//创建服务器端ServerSocket(ss),指定端口号8000
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
ServerSocket ss=new ServerSocket(8000);

_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
Socket s = ss.accept();
 DataOutputStream dos = new DataOutputStream(s.getOutputStream());
 DataInputStream dis = new DataInputStream(s.getInputStream());
 dos.writeUTF("你已经连上服务器了,告诉我你的姓名...");
 String name = dis.readUTF();
 dos.writeUTF("再见:" + name);
 s.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }

第4关:TCP通信实现奇偶数判断

ClientPlus

package step4;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

public class ClientPlus {

public static void main(String[] args) {
 Scanner sc = new Scanner(System._in_);
 ServerPlus server = new ServerPlus();
 server.start();
try {
//创建客户端Socket(s),指定服务器端IP地址和端口号
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
Socket s=new Socket("127.0.0.1",8000);

_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
 DataInputStream dis = new DataInputStream(s.getInputStream());
//客户端通过循环依次接收服务器返回的结果,并输入新的整数传递给服务器
_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
while(true){
 System._out_.println(dis.readUTF());
 String num=sc.next();
 dos.writeUTF(num);
 }

_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
} catch (EOFException e) {
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }

ServerPlus

package step4;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerPlus extends Thread {
@Override
public void run() {
try {

_/\*\*\*\*\*\*\*\*\*\* Begin \*\*\*\*\*\*\*\*\*\*/_
//创建服务器端ServerSocket(ss),指定端口号8000
ServerSocket ss=new ServerSocket(8000);

_/\*\*\*\*\*\*\*\*\*\* end \*\*\*\*\*\*\*\*\*\*/_
Socket s = ss.accept();
 DataOutputStream dos = new DataOutputStream(s.getOutputStream());
 DataInputStream dis = new DataInputStream(s.getInputStream());
 ReceiveThread thread = new ReceiveThread(s, dos, dis);
 thread.start();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }

ReceiveThread

package step4;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;

class ReceiveThread extends Thread {
 DataOutputStream dos;
 DataInputStream dis;
 Socket s;
public ReceiveThread(Socket s, DataOutputStream dos, DataInputStream dis) {
this.s = s;
this.dos = dos;
this.dis = dis;
 }
@Override
public void run() {
try {
dos.writeUTF("请输入一个整数,我知道是奇数还是偶数");
while(true) {
 String num = dis.readUTF();
if("-1".equals(num)) {
s.close();
break;
 }
 String result = (Integer._parseInt_(num)%2==0)?"偶数":"奇数";
dos.writeUTF(num + "是..." + result);
 }
 } catch (SocketException e) {
try {
s.close();
 } catch (IOException e1) {
 e1.printStackTrace();
 }
 } catch (IOException e) {
 System._out_.println("数据读取异常");
 }
 }
 }

第1关:字节输入/输出流实现数据的保存和读取

任务描述

本关任务:编写应用程序(SortArray.java),使用字节输入/输出流实现数据的保存和读取。

相关知识

Java 流功能相关的类都封装在 java.io包中,所以要使用流类,必须导入java.io包。数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别,流的分类如图1所示。

图1 流的分类

  • 根据数据流向的不同,主要分为输入(Input)流和输出(Output)流两大类。输入流只能从其读取数据而不能向其写入数据,同样对于输出流只能向其写入数据而不能从其读取数据。
  • 数据流按照数据类型的不同分为字节流和字符流。字节流(byte stream)以字节为单位进行数据传输,可用于读写二进制数据。字节流类一般以InputStream/OutputStream结尾。字符流(character stream)以字符为单位进行数据传输,一个字符由两个字节组成,用于处理文本数据,支持Unicode编码。字符流一般以Reader/Writer结尾。

Java提供了专门用于输入/输出功能的包java.io,其中包括5个非常重要的类,InputStream、OutputStream、Reader、Writer和File。其他与输入/输出有关的类均是这5个类基础上的扩展。

File 类

借助File対象可以获取文件和相关目录的属性信息,其主要方法如表2所示。

表2 File类的主要方法

方法 功能
String getName() 返回文件名
--- ---
String getPath() 返回文件或目录路径
String getAbsolutePath() 返回绝对路径
String getParent() 获取文件所在口录的父目录
boolean exists() 文件是否存在
boolean canWrite() 文件是否可写
boolean canRead() 文件是否可读
boolean isFile() 是否为一个正确定义的文件
boolean isDirectory() 是否为目录
long lastModified() 文件的最后修改日期
long length() 文件长度
boolean mkdir() 创建当前目录的子目录
String[] list() 列出目录中的文件
boolean renameTo(File newFile) 将文件改名为新文件名
void delete() 删除文件
boolean equals(File f) 比较两个文件或目录是否相等

面向字节的输入与输出流

字节流是从InputStream(输入流类)和OutputStream(输出流类)派生出来的一系列类,这类流以字节(Byte)为基本处理单位。它们除了可以用来处理二进制文件的数据之外,也可以用来处理文本文件。 注意:虽然字节流可以操作文本文件,但不提倡这样做,因为用字节流操作文本文件,如果文件中有汉字,可能会出现乱码。这是因为字节流不能直接操作Unicode字符所致。因此Java语言不提倡使用字节流读写文本文件,而建议使用字符流操作文本文件。

InputStream和OutputStream都是抽象类,不能直接使用,所以在具体应用时使用的都是由它们所派生出来的子类,用于不同情况数据的输入/输出操作。

(1) ** Inputstream **是面向字节的输入流的根 它是所有字节输入流的父类,其层次结构如图2所示:

图2 字节输入流层次结构

InputStream类定义了字节输入流的基本操作,如读取数据和关闭输入流等功能。InputStream类中所有方法遇到错误时都会引发 IOException异常。InputStream类的常用方法如表2所示。

表2 类InputStream的方法

方法 功能
int read() 读一个字节
--- ---
int read (byte b[]) 读多个字节到字节数组
int read(byte[] b, int off, int len) 读指定长度的数据到字节数组,数据从字节数组的off处开始存放
long skip(long n) 输入指针跳过n个字节
void mark() 在当前指针位置处做一标记
void reset() 将位置指针返回标记处
void close() 关闭流

类OutputStream是面向字节输出流的根,是所有字节输出流的父类,其层次结构如图 4 所示。

图4 字节输出流层次结构

OutputStream类定义了字节输出流的基本操作,如输出数据和关闭输出流等功能。OutputStream类中所有方法遇到错误时也会引发 IOException异常。OutputStream类的常用方法如表3所示。。

表3 类OutputStream的方法

方法 功能
void write(int b) 将指定字节的数据内容写入到输出流。注意这里的参数b是int类型,但实际写入到输出流的只是b的低8位数据,高24位数据被忽略
--- ---
void write(byte b[]) 将指定字节数组的内容写入输出流
void write(byte b[], int off, int len) 将指定字节数组从 off 位置开始的 len 字节的内容写入到输出流
void flush() 实际的输出流的实现从性能上考虑,往往不会将每次write操作的数据都写到目标数据源去,而是将数据先缓存起来,再一次性写到目标数据源。flush()方法是针对这种情况的输出流定义的,它的作用是刷新输出流,强行将缓冲区的内容写入到输出流
void close() 关闭数据流,当完成对数据流的操作之后需要关闭数据流

(2) 流的关闭 在Java编程过程中,如果打开了外部资源(文件、数据库连接、网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们。因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在编程时确保在正确的时机关闭外部资源,就会导致外部资源泄露,紧接着就会出现文件被异常占用,数据库连接过多导致连接池溢出等诸多很严重的问题。 为了确保外部资源一定要被关闭,通常关闭代码被写入finally代码块中,当然我们还必须注意到关闭资源时可能抛出的异常,因此关闭流一般采用如下模式的代码:

  1. FileInputStream inputStream =null;
  2. try{
  3. inputStream =newFileInputStream(newFile("test"));
  4. // 与流相关的操作代码
  5. }catch(IOException e){
  6. //异常处理代码
  7. }finally{
  8. if(inputStream !=null){
  9. try{
  10. inputStream.close();
  11. }catch(IOException e){
  12. e.printStackTrace();
  13. }
  14. }
  15. }

在finally代码块中关闭流,这样可以保证无论是正常使用还是出现异常,流最终都会被关闭。但这种书写方式过于复杂。在JDK7中新增了try-with-resources语法,当一个外部资源对象(比如FileInputStream对象)实现了java.lang.AutoCloseable接口,那么就可以将上面的代码简化为如下形式:

  1. try(FileInputStream inputStream =newFileInputStream(newFile("test"))){
  2. // 与流相关的操作代码
  3. }catch(IOException e){
  4. //异常处理代码
  5. }

创建外部资源对象的代码放在try关键字后面的括号中,当有多个资源时用";"分隔开,当这个try-catch代码块执行完毕后,Java会确保外部资源的close方法被调用。自动关闭资源的try语句相当于包含了隐式的finally语句块,该语句块会自动调用实现了AutoCloseable接口的资源对象的close()方法关闭所访问的资源对象。

**(3) FileInputStream****和 **FileOutputStream FileInputStream和FileOutputStream分别是InputStream和OutputStream的直接子类,这两个子类主要是负责完成对本地磁盘文件的顺序输入输出操作的流。在进行文件的读写操作时会产生IOException异常,该异常必须捕获或声明抛出。

**(4) DataInputStream****和 **DataOutputStream 有时按字节为基本单位进行数据的读写处理并不方便,如一个二进制文件中存放有100个整数值,从中读取时,自然希望按照int为基本单位进行读取,每次读取一个整数值,而不是每次读取1字节。Java语言中按照基本数据类型进行读写的就是数据输入流类DataInputStream和数据输出流类DataOutputStream,它们分别是过滤字节输入流FilterInputStream和过滤字节输出流FilterOutputStream的子类,同时分别实现了DataInput和DataOutput接口。

在DataInput和DataOutput接口中定义了独立于具体机器的带格式的读写操作,从而实现了对不同类型数据的读写。DataInput接口中描述了用于从输入流中读取基本类型值的一组方法,即从输入流中成组地读取字节,并将其视为基本类型值。而DataOutput接口中描述了将基本类型值写入输出流中的一组方法。

DataInputStream 是用来装饰其它输入流,它允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型,它允许应用程序将基本Java数据类型写到基础输出流中。利用类DataInputStream和DataOutputStream处理字节或字节数组,实现对基本数据类型数值的读写,提供的读写方法包括read()、readByte()、readInt()、write()、writeChar()、writeBoolean()等等。

数据输入流类DataInputStream和数据输出流类DataOutputStream的构造方法如下:

  • DataInputStream(InputStream in):建立一个新的数据输入流,从指定的输入流in读数据。
  • DataOutputStream(OutputStream out):建立一个新的数据输出流,向指定的输出流out写数据。

面向字符的输入与输出流

(1) 类Reader是面向字符的输入流的根,其提供的方法与InputStream类似,只是将基于Byte的参数改为基于Char。 (2) 类Writer是面向字符的输出流类的根,其提供的方法与OutputStream类似,只是将基于Byte的参数改为基于Char。

文件的顺序读写

(1) 面向字节的文件访问 面向字节的文件访问以二进制文件作为数据源,FilelnputStream类和FileOutputStream类分别用于文件的读、写访问。 利用InputStream和Outputstream的方法可实现文件的读写操作。 可用 DataInputStream对 FilelnputStream流进行过滤;用 DataOuputStream对FileOutputStream流进行过滤,以实现具体数据类型数据的读写。 (2) 面向字符的文件访问 面向字符的文件访问以字符文件作为数据源,包括FileReader类和FileWriter类,分别用于字符文件的读、写访问。 基于字符流的数据读写方法与基于字节流的类似,只是将读写的单位由字节改为字符,方法中的字节数组参数相应改为字符数组即可。例如,int read(char b[])表示从文件读数据填满数组,返回读到的字符数。 可用 BufferedReader对 FileReader流进行过滤;用 BufferedWriter对 FileWriter流进行过滤,其中包含newLine()方法,可用于写入一个换行。

编程要求

根据提示,在右侧编辑器补充代码。 编写应用程序(SortArray.java),使用字节输入/输出流实现数据的保存和读取。 要求功能如下:

  • 输入1~100之间的整型数据保存到数组(数组大小为10)中
  • 将数组排序(由小到大)后的元素保存到指定的文件中
  • 如果文件不存在,则创建文件
  • 从文件中读取排序后的元素并按指定格式输出
  • 利用异常处理机制处理可能发生的错误

测试说明

平台会对你编写的代码进行测试:

测试输入:70 94 21 43 76 60 70 37 75 80 ;

预期输出: 21<37<43<60<70<70<75<76<80<94

第2关:字符输入/输出流实现发送电报

任务描述

本关任务:编写应用程序(Encrypt.java),使用字符输入/输出流实现发送电报的功能。

相关知识

什么是字节

字节是指一小组相邻的二进制数码。通常是8位作为一个字节。它是构成信息的一个小单位,并作为一个整体来参加操作,比字小,是构成字的单位。

字节(Byte) 是一种计量单位,表示数据量的多少,它是计算机信息技术用于计量存储容量的一种计量单位.

什么是字符

我们想象一下,给你一串二进制码,要你来分辨它是什么含义,是代表数字还是字母还是汉字,你能有效的分辨吗?

显然不能,一般来说,我们是比较难以理解一串二进制码代表的含义的,而且一串二进制码是代表什么含义也无法很直观的表示出来。

我们比较好识别的是文字,字母和符号。

所以就有了字符,字符是指计算机中使用的文字和符号,比如1、2、3、A、B、C、~!·#¥%……—*()——+、等等。

字符在计算机中可以看做: 字节** + **编码表

什么意思呢?

我们知道,计算机是只识别二进制的,但是我们日常操作电脑,需要输入文字,字母,数字这些,我们不可能先去记住一串二进制数字,比如说A这个字母的二进制是什么,因为这样太麻烦,也记不住,所以 编码表 ,就诞生了,编码表的作用就是在我们进行输入的时候, 将我们输入的字符转换成计算机能识别的二进制 ,在我们阅读数据的时候,将二进制转换成我们人能识别的文字字母和数字。

最先普及的就要数ASCII码表了,ASCII码表是美国信息交换标准代码,是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。

看到这你肯定会有疑问,这ASCII码表只有英语和西欧语呀,那汉语呢,其他语言呢?

是的,自从ASCII码表推出之后,很多国家也都推出了本国语言的编码表。像中国就有GB2312,GBK等等。

现在我们一起设想一个场景,当我们编辑一个文本文件,输入了很多字符,这些字符都用ASCII码表编码,然后我们查看这个文本文件的时候,是使用的GBK码表解码,会出现什么问题吗?

相信你已经有答案了,这会出现软件开发中非常常见的问题: 乱码

当我们对字节进行编码的时候使用的是一种编码表,而解码的时候使用的是另一种编码表的时候,就会出现乱码的问题了,是因为每一个编码表,它的字符对应二进制的字节是不一致的。

但是互联网是一个互联互通的平台,所以如果每个国家都使用自己的一套编码器,就会出现许多问题。

在1992年的时候,推出了UTF-8编码规范,是一种针对Unicode的可变长度字符编码,又称万国码,UTF-8用1到6个字节编码Unicode字符。用在网页上可以统一页面显示中文简体繁体及其它语言(如英文,日文,韩文)。

UTF-8也是我们目前在应用开发中使用的最多的编码格式。

Java中默认采用的是Unicode编码格式(具体来说是UTF-16编码)。

什么是IO流

IO流中的IO是Input,Output,输入和输出的意思,是用来处理设备与设备之间的数据传输的,不仅能处理内部设备(比如CPU、GPU、内存),还能处理外部设备(比如手机和PC,客户端与服务器)。

在Java中定义数据 按照流向 ,分为 输入流输出流

首先我们来了解输入流,从字面上就很容易理解,凡是从外部流入的数据都可以通过输入流来处理。比如读取文件。

输出流,就表示从内部流出的数据,比如:我们编辑了一个文本文件,当我们按下ctrl+s的时候,就将该文件从内存保存到了硬盘,这就是一个将数据从内存中输出到硬盘的过程。

除了输出和输入流,流按照操作的数据还分为: 字节流字符流

总体结构如下图:

输入流

我们通过一个示例,来看看输入流应该如何使用,首先我们在D盘下创建一个hello.txt文件。输入文本Hello Java Hello InputStream。

在main方法中加入如下代码:

输出:

Hello Java Hello InputStream

代码解释:

这个例子我们主要目的是,读取文件中的数据并将数据显示在控制台。

实现步骤是:首先读取文件转换成文件输入流(FileInputStream),然后定义一个字节数组作为容器用来存储即将读取到的数据。fs.read(b)函数的作用是将数据读取到b数组中,最后通过编码表,将字节数组编码成字符。

输出流

我们使用输出流将字符串hello educoder写入到一个文件中:

运行这段代码,打开D盘下你会发现test.txt文件被创建了,并且文件的内容是hello educoder。

代码解释:

最佳实践 上面作为示例的两段代码都是存在很大问题的,什么问题呢?

因为在Java中对于流的操作是非常消耗资源的,如果我们使用流对一个资源进行操作了之后却没有释放它的资源,这就会造成系统资源的浪费,如果积累了很多这种空置的资源,最后可能会导致系统崩溃。

上述代码的最佳实践为:

  1. OutputStream out =null;
  2. try{
  3. String file ="D://test.txt";
  4. out =newFileOutputStream(file);
  5. String str ="hello educoder";
  6. byte[] b = str.getBytes();
  7. out.write(b);
  8. out.flush();
  9. }catch(Exception e){
  10. e.printStackTrace();
  11. }finally{
  12. if(out !=null){
  13. try{
  14. out.close();// 释放该输出流
  15. }catch(IOException e){
  16. e.printStackTrace();
  17. }
  18. }
  19. }

核心就是在使用完流之后,释放它所占用的资源。

Writer 字符流的使用很简单,和字节输入流类似,以FileWriter举例:

执行上述代码即可看到在D盘下创建了一个名为hello.txt的文件,文件的内容为hello。

上面代码fw.flush()和fw.close()也可以省略fw.flush(),只写fw.close()就可以了,但是都省略是不对的,如果都省略你会发现文本没有写入到hello.txt文件。

Reader Reader的使用也很简单,以FileReader为例:

输出:

hello+ 1019个空格

使用上述代码的会输出hello.txt中的内容,但是会有一个问题:输出hello的同时还输出了1019个空格,这是什么原因呢,如何解决这些问题呢?请你思考。

在上面我们遇到了一个问题:hello.txt文件只有五个字符,而用来存储字符的数组有1024个字符,直接使用FileReader的read()方法读取然后输出就会有1019个空字符,如何来解决这个问题呢?

很容易想到的方法就是,我们定义一个长度为5的字符数组就可以了,这样确实可以暂时解决问题,可是我们往往不知道读取的文件有多大,如果文件中不止5个字符,而是有几万个字符我们又应该怎么办呢?

这就需要我们深入的了解IO流的常用函数了。

read()****方法 我们来看read方法的详细解释:

方法 参数说明 方法描述
int read(char[] cbuf) 字符数组 将字符流中的数据读入到字符数组中,如果读取到文件末尾则返回-1,否则返回读取到的长度
--- --- ---

理解了read方法,之前的问题就好解决了。

代码:

  1. String file ="D://hello.txt";
  2. FileReader fr =newFileReader(file);
  3. char[] cbuf =newchar[1024];
  4. int len = fr.read(cbuf);//将数据读入到cbuf中并返回读取到的数据长度
  5. StringBuilder builder =newStringBuilder();
  6. builder.append(cbuf,0,len);//将cbuf 0 到len长度的数据添加到builder
  7. System.out.println(builder.toString());

运行这段代码,我们会发现输出是正确的,没有再打印出多余的空格。

可能我们又会有疑问了,如果文本文件大于1K,这段代码肯定就行不通了,怎么办呢?

很简单,加个循环就可以啦:

  1. String file ="D://hello.txt";
  2. FileReader fr =newFileReader(file);
  3. char[] cbuf =newchar[1024];
  4. int len =0;// 每次读取的长度
  5. StringBuilder builder =newStringBuilder();
  6. while((len = fr.read(cbuf))!=-1){
  7. builder.append(cbuf,0,len);
  8. }
  9. System.out.println(builder.toString());

这样修改之后我们就可以读取任意的文件,并将其内容输出到控制台了。

write()****方法write()方法有两种常用的重载方法:

方法 参数说明 方法描述
void write(String str) 字符串 将字符串写入到字符流中
--- --- ---
void write(char[] cbuf,int off,int len) 字符数组,偏移量(从什么位置开始),写入的长度 将字符数组按照偏移量写入到字符流中写入的长度和偏移量与len有关

理解了这两种方法,我们现在如果要复制一个文本文件就很方便了,现在我们就来将D盘下hello.txt文件复制到E盘下,并重命名为abc.txt:

  1. FileReader fr =newFileReader("D://hello.txt");//定义FileReader读取文件
  2. int len =0;//每次读取的字符数量
  3. char[] cbuf =newchar[1024];//每次读取数据的缓冲区
  4. FileWriter fw =newFileWriter("E://abc.txt");//定义FileWriter写文件
  5. while((len = fr.read(cbuf))!=-1){
  6. fw.write(cbuf,0,len);
  7. }
  8. fw.close();//释放资源 刷新缓冲区
  9. fr.close();

这段代码就是一个边读边写的过程,运行之后我们发现E盘下已经有了abc.txt文件并且内容和hello.txt一致。

使用字节流读写文件 到目前为止我们一直操作的都是文本文件,不过我们计算机中存储的文件可不止有文本文件,还有很多其他类型的,比如图片,视频,等等。

如果要对非文本类型的文件进行操作,应该怎么做呢?这个时候字符流还能不能派上用场呢?

答案是否定的,字符流只适用于操作字符类型的文件,不能操作非字符类型的。

所以这个时候应该用什么来操作呢?

相信你已经想到了:字节流。

是的我们需要使用字节流来操作非字符类文件。

接下来,我们使用字节流来复制一个图片文件,代码:

  1. FileInputStream fs =newFileInputStream("D://user.jpg");//定义文件输入流读取文件信息
  2. FileOutputStream fos =newFileOutputStream("E://new.jpg");//定义文件输出流写文件
  3. int len =0;//每次读取数据的长度
  4. byte[] bys =newbyte[1024];//数据缓冲区
  5. while((len = fs.read(bys))!=-1){
  6. fos.write(bys,0, len);
  7. }
  8. //释放资源 刷新缓冲区
  9. fs.close();
  10. fos.close();

运行即可看到E盘下生成了一个名为new.jpg的文件,且内容和user.jpg一致 可以发现上述代码和之前的字符流很像,确实原理都是类似的。

可能学到这,你会有很多疑问:

  1. 字节流既然可以用来读取非字符构成的文件,那可以读取字符类型的文件吗? 答案是可以的,字节流可以操作所有类型的文件,因为计算机中的数据都是以字节的方式存储的;
  2. 既然字节流可以用来操作所有的文件,那还要字符流干啥咧? 因为字符流操作字符类型的数据和文件要比字节流快很多。

扩展 使用BufferedReader读取字符文件的速度要比我们之前使用的字节流和FileReader快很多,示例代码:

  1. BufferedReader bf =newBufferedReader(newFileReader("D://hello.txt"));
  2. BufferedWriter writer =newBufferedWriter(newFileWriter("D://abc.txt"));
  3. String str ="";
  4. while((str = bf.readLine())!=null){
  5. writer.write(str);
  6. }
  7. bf.close();
  8. writer.close();

编程要求

根据提示,在右侧编辑器补充代码。

  • 电报内容经过加密后使用字符输出流存储在指定的文件中
  • 如果文件不存在,则创建文件
  • 使用字符输入流从文件中分别读取密文和明文的内容并显示出来
  • 利用异常处理机制处理可能发生的错误

注意 :在对字符加密时采用了异或(q)的简单字符加密处理,解密时也要进行逐个字符异或(q)的处理。

测试说明

平台会对你编写的代码进行测试:

测试输入: 今晚10点发动总攻

预期输出: 密文: 亻昫@A烈厠務恊敊 明文: 今晚10点发动总攻

第3关:简单TCP通信

任务描述

本关任务:编写简单的TCP通信程序。

相关知识

InetAddress类的使用

在JDK中,提供了一个与IP地址相关的InetAddress类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法,下表中列举了InetAddress类的一些常用方法。

上表中,列举了InetAddress的五个常用方法。其中,前两个方法用于获得该类的实例对象,第一个方法用于获得表示指定主机的InetAddress对象,第二个方法用于获得表示本地的InetAddress对象。通过InetAddress对象便可获取指定主机名,IP地址等。

代码示例:

  1. import java.net.InetAddress;
  2. publicclassInetAddressDemo{
  3. publicstaticvoid main(String[] args)throwsException{
  4. InetAddress address =InetAddress.getLocalHost();
  5. // 获取主机名
  6. String name = address.getHostName();
  7. // 获取IP地址
  8. String ip = address.getHostAddress();
  9. System.out.println(name +"---"+ ip);
  10. }
  11. }

Socket通信原理

  1. 客户端和服务端分别用Socket和ServerSocket类实现连接。
  2. Socket的工作过程是服务端首先执行等待连接,根据指定端口建立ServerSocket 对象,通过该对象的accept方法监听客户连接,客户端创建Socket对象请求与服务端的特定端口进行连接,连接成功后,双方将建立一条Socket通道,利用Socket对象的 getInputStream()和getOutputStream()方法可得到对Socket通道进行读写操作的输入/输出流,通过流的读写实现客户与服务器的通信。
  3. Java Socket通信编程经常结合多线程技术,一个服务器可以和多个客户机建立连接,进行并发通信。

基于流套接字的客户/服务器模型的通信过程如图1所示。

图1 基于流套接字的客户/服务器模型的通信过程

基于流套接字的客户/服务器通信的基本步骤如下:

  1. 在服务器端创建一个ServerSocket对象,并指定端口号,或根据需要指定允许连接的最大用户数。
  2. 运行ServerSocket对象的accept()方法,等候客户端请求。
  3. 客户端创建一个Socket对象,指定连接服务器的IP地址和端口号,向服务器发出连接请求。
  4. 服务器端接收到客户端连接请求后,创建服务器端Socket对象与客户端建立连接。
  5. 服务器和客户端分别建立输入输出流,进行数据传输。
  6. 通信结束后,服务器和客户端分别关闭各自的Socket连接。
  7. 服务器端程序运行结束后,调用ServerSocket对象的close()方法停止TCP连接服务。

编程要求

根据提示,在右侧编辑器补充代码,使用Socket和ServerSocket套接字编程,完成简单的TCP通信。

测试说明

平台会对你编写的代码进行测试:

测试输入:张三

预期输出: 你已经连上服务器了,告诉我你的姓名... 再见:张三

第4关:TCP通信实现奇偶数判断

任务描述

本关任务:编写TCP通信程序完成奇偶数判断。

相关知识

在上面简单TCP通信实验中,客户端和服务器端只能完成一次TCP通信,程序就停止运行。 如果客户端要与服务器进行多次TCP通信,则服务器端需要循环接收客户端发送的数据并处理。同时,为了防止因 readUTF()方法而阻塞服务器端主线程的运行,其他客户机不能同时连接服务器了。因此,有必要将与每个客户端的通信任务从主线程中分离出去,这就需要为每个客户端创建通信子线程ReceiveThread,用于接收连接指定客户端的数据并对此数据进行处理。 下面通信子线程ReceiveThread通过String num = dis.readUTF()代码接收客户端读取过来的整数,然后判断该整数的奇偶性,并通过dos.writeUTF(num + "是..." + result)代码将结果返回给客户端。当接收的整数值为-1时,程序结束运行。 通信子线程ReceiveThread程序代码如下所示:

  1. import java.io.DataInputStream;
  2. import java.io.DataOutputStream;
  3. import java.io.IOException;
  4. import java.net.Socket;
  5. import java.net.SocketException;
  6. classReceiveThreadextendsThread{
  7. DataOutputStream dos;
  8. DataInputStream dis;
  9. Socket s;
  10. publicReceiveThread(Socket s,DataOutputStream dos,DataInputStream dis){
  11. this.s = s;
  12. this.dos = dos;
  13. this.dis = dis;
  14. }
  15. @Override
  16. publicvoid run(){
  17. try{
  18. dos.writeUTF("请输入一个整数,我知道是奇数还是偶数");
  19. while(true){
  20. String num = dis.readUTF();
  21. //当输入整数为-1时,程序结束运行
  22. if("-1".equals(num)){
  23. s.close();
  24. break;
  25. }
  26. String result =(Integer.parseInt(num)%2==0)?"偶数":"奇数";
  27. dos.writeUTF(num +"是..."+ result);
  28. }
  29. }catch(SocketException e){
  30. try{
  31. s.close();
  32. }catch(IOException e1){
  33. e1.printStackTrace();
  34. }
  35. }catch(IOException e){
  36. System.out.println("数据读取异常");
  37. }
  38. }
  39. }

客户端通过循环依次接收服务器返回的结果,并输入新的整数传递给客户端进行奇偶性判断。

  1. while(true){
  2. System.out.println(dis.readUTF());
  3. String num = sc.next();
  4. dos.writeUTF(num);
  5. }

同学们也可以实现多个客户端同时连接服务器进行通信的程序,服务器端相关代码如下所示:

  1. publicclassServerPlus{
  2. publicstaticvoid main(String[] args){
  3. try{
  4. //创建服务器端ServerSocket ,指定端口号8000
  5. ServerSocket ss =【补充代码】;
  6. while(true){
  7. Socket s = ss.accept();
  8. DataOutputStream dos =newDataOutputStream(s.getOutputStream());
  9. DataInputStream dis =newDataInputStream(s.getInputStream());
  10. ReceiveThread thread =newReceiveThread(s, dos, dis);
  11. thread.start();
  12. }
  13. }catch(IOException e){
  14. e.printStackTrace();
  15. }
  16. }
  17. }

由于实训平台限制,无法完成多客户端相关实验内容,请同学们查看实验指导书自行完成多客户端通信。

编程要求

根据提示,在右侧编辑器补充代码,对输入的整数进行奇偶性判断;当输入的整数为-1时,程序结束运行。

测试说明

平台会对你编写的代码进行测试:

测试输入:1 2 3 4 -1

预期输出: 请输入一个整数,我知道是奇数还是偶数 1是...奇数 2是...偶数 3是...奇数 4是...偶数

posted @ 2023-05-09 23:57  梦羽儿  阅读(503)  评论(3编辑  收藏  举报