Java I/O 之工具类
Java I/O 之 BIO
BIO
BIO(blocking IO) 即阻塞 IO。指的主要是传统的 java.io
包,它基于流模型实现。流从概念上来说是一个连续的数据流。当程序需要读数据的时候就需要使用输入流读取数据,当需要往外写数据的时候就需要输出流。
java.io
包提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。很多时候,人们也把 java.net 下面提供的部分网络 API,比如 Socket
、ServerSocket
、HttpURLConnection
也归类到同步阻塞 IO 类库,因为网络通信同样是 IO 行为。
BIO 中操作的流主要有两大类,字节流和字符流,两类根据流的方向都可以分为输入流和输出流。
- 字节流
- 输入字节流:
InputStream
- 输出字节流:
OutputStream
- 输入字节流:
- 字符流
- 输入字符流:
Reader
- 输出字符流:
Writer
- 输入字符流:
字节流
字节流主要操作字节数据或二进制对象。
字节流有两个核心抽象类:InputStream
和 OutputStream
。所有的字节流类都继承自这两个抽象类。
InputStream
InputStream
用于从源头(通常是文件)读取数据(字节信息)到内存中,java.io.InputStream
抽象类是所有字节输入流的父类。
InputStream
常用方法:
read()
:返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回-1
,表示文件结束。read(byte b[ ])
: 从输入流中读取一些字节存储到数组b
中。如果数组b
的长度为零,则不读取。如果没有可用字节读取,返回-1
。如果有可用字节读取,则最多读取的字节数最多等于b.length
, 返回读取的字节数。这个方法等价于read(b, 0, b.length)
。read(byte b[], int off, int len)
:在read(byte b[ ])
方法的基础上增加了off
参数(偏移量)和len
参数(要读取的最大字节数)。skip(long n)
:忽略输入流中的 n 个字节 , 返回实际忽略的字节数。available()
:返回输入流中可以读取的字节数。close()
:关闭输入流释放相关的系统资源。
从 Java 9 开始,InputStream
新增加了多个实用的方法:
readAllBytes()
:读取输入流中的所有字节,返回字节数组。readNBytes(byte[] b, int off, int len)
:阻塞直到读取len
个字节。transferTo(OutputStream out)
:将所有字节从一个输入流传递到一个输出流。
OutputStream
OutputStream
用于将数据(字节信息)写入到目的地(通常是文件),java.io.OutputStream
抽象类是所有字节输出流的父类。
OutputStream
常用方法:
write(int b)
:将特定字节写入输出流。write(byte b[ ])
: 将数组b
写入到输出流,等价于write(b, 0, b.length)
。write(byte[] b, int off, int len)
: 在write(byte b[ ])
方法的基础上增加了off
参数(偏移量)和len
参数(要读取的最大字节数)。flush()
:刷新此输出流并强制写出所有缓冲的输出字节。close()
:关闭输出流释放相关的系统资源。
文件字节流
FileOutputStream
和 FileInputStream
提供了读写字节到文件的能力。
文件流操作一般步骤:
- 使用
File
类绑定一个文件。 - 把
File
对象绑定到流对象上。 - 进行读或写操作。
- 关闭流
FileOutputStream
和 FileInputStream
示例:
public class FileStreamDemo {
private static final String FILEPATH = "temp.log";
public static void main(String[] args) throws Exception {
write(FILEPATH);
read(FILEPATH);
}
public static void write(String filepath) throws IOException {
// 第 1 步、使用 File 类找到一个文件
File f = new File(filepath);
// 第 2 步、通过子类实例化父类对象
OutputStream out = new FileOutputStream(f);
// 实例化时,默认为覆盖原文件内容方式;如果添加 true 参数,则变为对原文件追加内容的方式。
// OutputStream out = new FileOutputStream(f, true);
// 第 3 步、进行写操作
String str = "Hello World\n";
byte[] bytes = str.getBytes();
out.write(bytes);
// 第 4 步、关闭输出流
out.close();
}
public static void read(String filepath) throws IOException {
// 第 1 步、使用 File 类找到一个文件
File f = new File(filepath);
// 第 2 步、通过子类实例化父类对象
InputStream input = new FileInputStream(f);
// 第 3 步、进行读操作
// 有三种读取方式,体会其差异
byte[] bytes = new byte[(int) f.length()];
int len = input.read(bytes); // 读取内容
System.out.println("读入数据的长度:" + len);
// 第 4 步、关闭输入流
input.close();
System.out.println("内容为:\n" + new String(bytes));
}
}
内存字节流
ByteArrayInputStream
和 ByteArrayOutputStream
是用来完成内存的输入和输出功能。
内存操作流一般在生成一些临时信息时才使用。 如果临时信息保存在文件中,还需要在有效期过后删除文件,这样比较麻烦。
ByteArrayInputStream
和 ByteArrayOutputStream
示例:
public class ByteArrayStreamDemo {
public static void main(String[] args) {
String str = "HELLOWORLD"; // 定义一个字符串,全部由大写字母组成
ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 准备从内存 ByteArrayInputStream 中读取内容
int temp = 0;
while ((temp = bis.read()) != -1) {
char c = (char) temp; // 读取的数字变为字符
bos.write(Character.toLowerCase(c)); // 将字符变为小写
}
// 所有的数据就全部都在 ByteArrayOutputStream 中
String newStr = bos.toString(); // 取出内容
try {
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(newStr);
}
}
管道流
管道流的主要作用是可以进行两个线程间的通信。
如果要进行管道通信,则必须把 PipedOutputStream
连接在 PipedInputStream
上。为此,PipedOutputStream
中提供了 connect()
方法。
public class PipedStreamDemo {
public static void main(String[] args) {
Send s = new Send();
Receive r = new Receive();
try {
s.getPos().connect(r.getPis()); // 连接管道
} catch (IOException e) {
e.printStackTrace();
}
new Thread(s).start(); // 启动线程
new Thread(r).start(); // 启动线程
}
static class Send implements Runnable {
private PipedOutputStream pos = null;
Send() {
pos = new PipedOutputStream(); // 实例化输出流
}
@Override
public void run() {
String str = "Hello World!!!";
try {
pos.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
try {
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 得到此线程的管道输出流
*/
PipedOutputStream getPos() {
return pos;
}
}
static class Receive implements Runnable {
private PipedInputStream pis = null;
Receive() {
pis = new PipedInputStream();
}
@Override
public void run() {
byte[] b = new byte[1024];
int len = 0;
try {
len = pis.read(b);
} catch (IOException e) {
e.printStackTrace();
}
try {
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("接收的内容为:" + new String(b, 0, len));
}
/**
* 得到此线程的管道输入流
*/
PipedInputStream getPis() {
return pis;
}
}
}
对象字节流
ObjectInputStream 和 ObjectOutputStream 是对象输入输出流,一般用于对象序列化。
这里不展开叙述,想了解详细内容和示例可以参考:[Java 序列化](03.Java 序列化。md)
数据操作流
数据操作流提供了格式化读入和输出数据的方法,分别为 DataInputStream
和 DataOutputStream
。
DataInputStream
和 DataOutputStream
格式化读写数据示例:
public class DataStreamDemo {
public static final String FILEPATH = "temp.log";
public static void main(String[] args) throws IOException {
write(FILEPATH);
read(FILEPATH);
}
private static void write(String filepath) throws IOException {
// 1. 使用 File 类绑定一个文件
File f = new File(filepath);
// 2. 把 File 对象绑定到流对象上
DataOutputStream dos = new DataOutputStream(new FileOutputStream(f));
// 3. 进行读或写操作
String[] names = { "衬衣", "手套", "围巾" };
float[] prices = { 98.3f, 30.3f, 50.5f };
int[] nums = { 3, 2, 1 };
for (int i = 0; i < names.length; i++) {
dos.writeChars(names[i]);
dos.writeChar('\t');
dos.writeFloat(prices[i]);
dos.writeChar('\t');
dos.writeInt(nums[i]);
dos.writeChar('\n');
}
// 4. 关闭流
dos.close();
}
private static void read(String filepath) throws IOException {
// 1. 使用 File 类绑定一个文件
File f = new File(filepath);
// 2. 把 File 对象绑定到流对象上
DataInputStream dis = new DataInputStream(new FileInputStream(f));
// 3. 进行读或写操作
String name = null; // 接收名称
float price = 0.0f; // 接收价格
int num = 0; // 接收数量
char[] temp = null; // 接收商品名称
int len = 0; // 保存读取数据的个数
char c = 0; // '\u0000'
try {
while (true) {
temp = new char[200]; // 开辟空间
len = 0;
while ((c = dis.readChar()) != '\t') { // 接收内容
temp[len] = c;
len++; // 读取长度加 1
}
name = new String(temp, 0, len); // 将字符数组变为 String
price = dis.readFloat(); // 读取价格
dis.readChar(); // 读取、t
num = dis.readInt(); // 读取 int
dis.readChar(); // 读取、n
System.out.printf("名称:%s;价格:%5.2f;数量:%d\n", name, price, num);
}
} catch (EOFException e) {
System.out.println("结束");
} catch (IOException e) {
e.printStackTrace();
}
// 4. 关闭流
dis.close();
}
}
合并流
合并流的主要功能是将多个 InputStream
合并为一个 InputStream
流。合并流的功能由 SequenceInputStream
完成。
public class SequenceInputStreamDemo {
public static void main(String[] args) throws Exception {
InputStream is1 = new FileInputStream("temp1.log");
InputStream is2 = new FileInputStream("temp2.log");
SequenceInputStream sis = new SequenceInputStream(is1, is2);
int temp = 0; // 接收内容
OutputStream os = new FileOutputStream("temp3.logt");
while ((temp = sis.read()) != -1) { // 循环输出
os.write(temp); // 保存内容
}
sis.close(); // 关闭合并流
is1.close(); // 关闭输入流 1
is2.close(); // 关闭输入流 2
os.close(); // 关闭输出流
}
}
字符流
字符流主要操作字符,一般用于处理文本数据。
字符流有两个核心类:Reader
类和 Writer
。所有的字符流类都继承自这两个抽象类。
文件字符流
文件字符流 FileReader
和 FileWriter
可以向文件读写文本数据。
FileReader
和 FileWriter
读写文件示例:
public class FileReadWriteDemo {
private static final String FILEPATH = "temp.log";
public static void main(String[] args) throws IOException {
write(FILEPATH);
System.out.println("内容为:" + new String(read(FILEPATH)));
}
public static void write(String filepath) throws IOException {
// 1. 使用 File 类绑定一个文件
File f = new File(filepath);
// 2. 把 File 对象绑定到流对象上
Writer out = new FileWriter(f);
// Writer out = new FileWriter(f, true); // 追加内容方式
// 3. 进行读或写操作
String str = "Hello World!!!\r\n";
out.write(str);
// 4. 关闭流
// 字符流操作时使用了缓冲区,并在关闭字符流时会强制将缓冲区内容输出
// 如果不关闭流,则缓冲区的内容是无法输出的
// 如果想在不关闭流时,将缓冲区内容输出,可以使用 flush 强制清空缓冲区
out.flush();
out.close();
}
public static char[] read(String filepath) throws IOException {
// 1. 使用 File 类绑定一个文件
File f = new File(filepath);
// 2. 把 File 对象绑定到流对象上
Reader input = new FileReader(f);
// 3. 进行读或写操作
int temp = 0; // 接收每一个内容
int len = 0; // 读取内容
char[] c = new char[1024];
while ((temp = input.read()) != -1) {
// 如果不是-1 就表示还有内容,可以继续读取
c[len] = (char) temp;
len++;
}
System.out.println("文件字符数为:" + len);
// 4. 关闭流
input.close();
return c;
}
}
字节流转换字符流
我们可以在程序中通过 InputStream
和 Reader
从数据源中读取数据,然后也可以在程序中将数据通过 OutputStream
和 Writer
输出到目标媒介中
使用 InputStreamReader
可以将输入字节流转化为输入字符流;使用OutputStreamWriter
可以将输出字节流转化为输出字符流。
OutputStreamWriter
示例:
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
File f = new File("temp.log");
Writer out = new OutputStreamWriter(new FileOutputStream(f));
out.write("hello world!!");
out.close();
}
}
InputStreamReader
示例:
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
File f = new File("temp.log");
Reader reader = new InputStreamReader(new FileInputStream(f));
char[] c = new char[1024];
int len = reader.read(c);
reader.close();
System.out.println(new String(c, 0, len));
}
}
字节流 vs. 字符流
相同点:
字节流和字符流都有 read()
、write()
、flush()
、close()
这样的方法,这决定了它们的操作方式近似。
不同点:
- 数据类型
- 字节流的数据是字节(二进制对象)。主要核心类是
InputStream
类和OutputStream
类。 - 字符流的数据是字符。主要核心类是
Reader
类和Writer
类。
- 字节流的数据是字节(二进制对象)。主要核心类是
- 缓冲区
- 字节流在操作时本身不会用到缓冲区(内存),是文件直接操作的。
- 字符流在操作时是使用了缓冲区,通过缓冲区再操作文件。
选择:
所有的文件在硬盘或传输时都是以字节方式保存的,例如图片,影音文件等都是按字节方式存储的。字符流无法读写这些文件。
所以,除了纯文本数据文件使用字符流以外,其他文件类型都应该使用字节流方式。
I/O 工具类
File
File
类是 java.io
包中唯一对文件本身进行操作的类。它可以对文件、目录进行增删查操作。
createNewFille
可以使用 createNewFille()
方法创建一个新文件。
注:
Windows 中使用反斜杠表示目录的分隔符 \
。~~~~~~~~
Linux 中使用正斜杠表示目录的分隔符 /
。
最好的做法是使用 File.separator
静态常量,可以根据所在操作系统选取对应的分隔符。
【示例】创建文件
File f = new File(filename);
boolean flag = f.createNewFile();
mkdir
可以使用 mkdir()
来创建文件夹,但是如果要创建的目录的父路径不存在,则无法创建成功。
如果要解决这个问题,可以使用 mkdirs()
,当父路径不存在时,会连同上级目录都一并创建。
【示例】创建目录
File f = new File(filename);
boolean flag = f.mkdir();
delete
可以使用 delete()
来删除文件或目录。
需要注意的是,如果删除的是目录,且目录不为空,直接用 delete()
删除会失败。
【示例】删除文件或目录
File f = new File(filename);
boolean flag = f.delete();
list 和 listFiles
File
中给出了两种列出文件夹内容的方法:
list()
: 列出全部名称,返回一个字符串数组。listFiles()
: 列出完整的路径,返回一个File
对象数组。
list()
示例:
File f = new File(filename);
String str[] = f.list();
listFiles()
示例:
File f = new File(filename);
File files[] = f.listFiles();
RandomAccessFile
注:
RandomAccessFile
类虽然可以实现对文件内容的读写操作,但是比较复杂。所以一般操作文件内容往往会使用字节流或字符流方式。
RandomAccessFile
类是随机读取类,它是一个完全独立的类。
它适用于由大小已知的记录组成的文件,所以我们可以使用 seek()
将记录从一处转移到另一处,然后读取或者修改记录。
文件中记录的大小不一定都相同,只要能够确定哪些记录有多大以及它们在文件中的位置即可。
RandomAccessFile 写操作
当用 rw
方式声明 RandomAccessFile
对象时,如果要写入的文件不存在,系统将自行创建。
r
为只读;w
为只写;rw
为读写。
【示例】文件随机读写
public class RandomAccessFileDemo01 {
public static void main(String args[]) throws IOException {
File f = new File("d:" + File.separator + "test.txt"); // 指定要操作的文件
RandomAccessFile rdf = null; // 声明 RandomAccessFile 类的对象
rdf = new RandomAccessFile(f, "rw");// 读写模式,如果文件不存在,会自动创建
String name = null;
int age = 0;
name = "zhangsan"; // 字符串长度为 8
age = 30; // 数字的长度为 4
rdf.writeBytes(name); // 将姓名写入文件之中
rdf.writeInt(age); // 将年龄写入文件之中
name = "lisi "; // 字符串长度为 8
age = 31; // 数字的长度为 4
rdf.writeBytes(name); // 将姓名写入文件之中
rdf.writeInt(age); // 将年龄写入文件之中
name = "wangwu "; // 字符串长度为 8
age = 32; // 数字的长度为 4
rdf.writeBytes(name); // 将姓名写入文件之中
rdf.writeInt(age); // 将年龄写入文件之中
rdf.close(); // 关闭
}
}
RandomAccessFile 读操作
读取是直接使用 r
的模式即可,以只读的方式打开文件。
读取时所有的字符串只能按照 byte 数组方式读取出来,而且长度必须和写入时的固定大小相匹配。
public class RandomAccessFileDemo02 {
public static void main(String args[]) throws IOException {
File f = new File("d:" + File.separator + "test.txt"); // 指定要操作的文件
RandomAccessFile rdf = null; // 声明 RandomAccessFile 类的对象
rdf = new RandomAccessFile(f, "r");// 以只读的方式打开文件
String name = null;
int age = 0;
byte b[] = new byte[8]; // 开辟 byte 数组
// 读取第二个人的信息,意味着要空出第一个人的信息
rdf.skipBytes(12); // 跳过第一个人的信息
for (int i = 0; i < b.length; i++) {
b[i] = rdf.readByte(); // 读取一个字节
}
name = new String(b); // 将读取出来的 byte 数组变为字符串
age = rdf.readInt(); // 读取数字
System.out.println("第二个人的信息 --> 姓名:" + name + ";年龄:" + age);
// 读取第一个人的信息
rdf.seek(0); // 指针回到文件的开头
for (int i = 0; i < b.length; i++) {
b[i] = rdf.readByte(); // 读取一个字节
}
name = new String(b); // 将读取出来的 byte 数组变为字符串
age = rdf.readInt(); // 读取数字
System.out.println("第一个人的信息 --> 姓名:" + name + ";年龄:" + age);
rdf.skipBytes(12); // 空出第二个人的信息
for (int i = 0; i < b.length; i++) {
b[i] = rdf.readByte(); // 读取一个字节
}
name = new String(b); // 将读取出来的 byte 数组变为字符串
age = rdf.readInt(); // 读取数字
System.out.println("第三个人的信息 --> 姓名:" + name + ";年龄:" + age);
rdf.close(); // 关闭
}
}
System
System
类中提供了大量的静态方法,可以获取系统相关的信息或系统级操作,其中提供了三个常用于 IO 的静态成员:
System.out
- 一个 PrintStream 流。System.out 一般会把你写到其中的数据输出到控制台上。System.out 通常仅用在类似命令行工具的控制台程序上。System.out 也经常用于打印程序的调试信息(尽管它可能并不是获取程序调试信息的最佳方式)。System.err
- 一个 PrintStream 流。System.err 与 System.out 的运行方式类似,但它更多的是用于打印错误文本。一些类似 Eclipse 的程序,为了让错误信息更加显眼,会将错误信息以红色文本的形式通过 System.err 输出到控制台上。System.in
- 一个典型的连接控制台程序和键盘输入的 InputStream 流。通常当数据通过命令行参数或者配置文件传递给命令行 Java 程序的时候,System.in 并不是很常用。图形界面程序通过界面传递参数给程序,这是一块单独的 Java IO 输入机制。
【示例】重定向 System.out
输出流
import java.io.*;
public class SystemOutDemo {
public static void main(String args[]) throws Exception {
OutputStream out = new FileOutputStream("d:\\test.txt");
PrintStream ps = new PrintStream(out);
System.setOut(ps);
System.out.println("人生若只如初见,何事秋风悲画扇");
ps.close();
out.close();
}
}
【示例】重定向 System.err
输出流
public class SystemErrDemo {
public static void main(String args[]) throws IOException {
OutputStream bos = new ByteArrayOutputStream(); // 实例化
PrintStream ps = new PrintStream(bos); // 实例化
System.setErr(ps); // 输出重定向
System.err.print("此处有误");
System.out.println(bos); // 输出内存中的数据
}
}
【示例】System.in
接受控制台输入信息
import java.io.*;
public class SystemInDemo {
public static void main(String args[]) throws IOException {
InputStream input = System.in;
StringBuffer buf = new StringBuffer();
System.out.print("请输入内容:");
int temp = 0;
while ((temp = input.read()) != -1) {
char c = (char) temp;
if (c == '\n') {
break;
}
buf.append(c);
}
System.out.println("输入的内容为:" + buf);
input.close();
}
}
Scanner
Scanner
可以获取用户的输入,并对数据进行校验。
【示例】校验输入数据是否格式正确
import java.io.*;
public class ScannerDemo {
public static void main(String args[]) {
Scanner scan = new Scanner(System.in); // 从键盘接收数据
int i = 0;
float f = 0.0f;
System.out.print("输入整数:");
if (scan.hasNextInt()) { // 判断输入的是否是整数
i = scan.nextInt(); // 接收整数
System.out.println("整数数据:" + i);
} else {
System.out.println("输入的不是整数!");
}
System.out.print("输入小数:");
if (scan.hasNextFloat()) { // 判断输入的是否是小数
f = scan.nextFloat(); // 接收小数
System.out.println("小数数据:" + f);
} else {
System.out.println("输入的不是小数!");
}
Date date = null;
String str = null;
System.out.print("输入日期(yyyy-MM-dd):");
if (scan.hasNext("^\\d{4}-\\d{2}-\\d{2}$")) { // 判断
str = scan.next("^\\d{4}-\\d{2}-\\d{2}$"); // 接收
try {
date = new SimpleDateFormat("yyyy-MM-dd").parse(str);
} catch (Exception e) {}
} else {
System.out.println("输入的日期格式错误!");
}
System.out.println(date);
}
}
输出:
输入整数:20
整数数据:20
输入小数:3.2
小数数据:3.2
输入日期(yyyy-MM-dd):1988-13-1
输入的日期格式错误!
null
网络编程
关键词:
Socket
、ServerSocket
、DatagramPacket
、DatagramSocket
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net
包中提供了低层次的网络通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。java.net 包中提供了两种常见的网络协议的支持:
- TCP - TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP/ IP。
- UDP - UDP 是用户数据报协议的缩写,一个无连接的协议。提供了应用程序之间要发送的数据的数据包。
Socket 和 ServerSocket
套接字(Socket)使用 TCP 提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
Java 通过 Socket 和 ServerSocket 实现对 TCP 的支持。Java 中的 Socket 通信可以简单理解为:java.net.Socket
代表客户端,java.net.ServerSocket
代表服务端,二者可以建立连接,然后通信。
以下为 Socket 通信中建立建立的基本流程:
- 服务器实例化一个
ServerSocket
对象,表示服务器绑定一个端口。 - 服务器调用
ServerSocket
的accept()
方法,该方法将一直等待,直到客户端连接到服务器的绑定端口(即监听端口)。 - 服务器监听端口时,客户端实例化一个
Socket
对象,指定服务器名称和端口号来请求连接。 Socket
类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。- 在服务器端,
accept()
方法返回服务器上一个新的Socket
引用,该引用连接到客户端的Socket
。
连接建立后,可以通过使用 IO 流进行通信。每一个 Socket
都有一个输出流和一个输入流。客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送,以下是一些类提供的一套完整的有用的方法来实现 sockets。
ServerSocket
服务器程序通过使用 java.net.ServerSocket
类以获取一个端口,并且监听客户端请求连接此端口的请求。
ServerSocket 构造方法
ServerSocket
有多个构造方法:
方法 | 描述 |
---|---|
ServerSocket() | 创建非绑定服务器套接字。 |
ServerSocket(int port) | 创建绑定到特定端口的服务器套接字。 |
ServerSocket(int port, int backlog) | 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 |
ServerSocket(int port, int backlog, InetAddress address) | 使用指定的端口、监听 backlog 和要绑定到的本地 IP 地址创建服务器。 |
ServerSocket 常用方法
创建非绑定服务器套接字。 如果 ServerSocket
构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
这里有一些 ServerSocket
类的常用方法:
方法 | 描述 |
---|---|
int getLocalPort() | 返回此套接字在其上侦听的端口。 |
Socket accept() | 监听并接受到此套接字的连接。 |
void setSoTimeout(int timeout) | 通过指定超时值启用/禁用 SO_TIMEOUT ,以毫秒为单位。 |
void bind(SocketAddress host, int backlog) | 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
Socket
java.net.Socket
类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket
对象通过实例化 ,而 服务器获得一个 Socket
对象则通过 accept()
方法 a 的返回值。
Socket 构造方法
Socket
类有 5 个构造方法:
方法 | 描述 |
---|---|
Socket() | 通过系统默认类型的 SocketImpl 创建未连接套接字 |
Socket(String host, int port) | 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
Socket(InetAddress host, int port) | 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
Socket(String host, int port, InetAddress localAddress, int localPort) | 创建一个套接字并将其连接到指定远程主机上的指定远程端口。 |
Socket(InetAddress host, int port, InetAddress localAddress, int localPort) | 创建一个套接字并将其连接到指定远程地址上的指定远程端口。 |
当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。
Socket 常用方法
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。
方法 | 描述 |
---|---|
void connect(SocketAddress host, int timeout) | 将此套接字连接到服务器,并指定一个超时值。 |
InetAddress getInetAddress() | 返回套接字连接的地址。 |
int getPort() | 返回此套接字连接到的远程端口。 |
int getLocalPort() | 返回此套接字绑定到的本地端口。 |
SocketAddress getRemoteSocketAddress() | 返回此套接字连接的端点的地址,如果未连接则返回 null。 |
InputStream getInputStream() | 返回此套接字的输入流。 |
OutputStream getOutputStream() | 返回此套接字的输出流。 |
void close() | 关闭此套接字。 |
Socket 通信示例
服务端示例:
public class HelloServer {
public static void main(String[] args) throws Exception {
// Socket 服务端
// 服务器在 8888 端口上监听
ServerSocket server = new ServerSocket(8888);
System.out.println("服务器运行中,等待客户端连接。");
// 得到连接,程序进入到阻塞状态
Socket client = server.accept();
// 打印流输出最方便
PrintStream out = new PrintStream(client.getOutputStream());
// 向客户端输出信息
out.println("hello world");
client.close();
server.close();
System.out.println("服务器已向客户端发送消息,退出。");
}
}
客户端示例:
public class HelloClient {
public static void main(String[] args) throws Exception {
// Socket 客户端
Socket client = new Socket("localhost", 8888);
InputStreamReader inputStreamReader = new InputStreamReader(client.getInputStream());
// 一次性接收完成
BufferedReader buf = new BufferedReader(inputStreamReader);
String str = buf.readLine();
buf.close();
client.close();
System.out.println("客户端接收到服务器消息:" + str + ",退出");
}
}
DatagramSocket 和 DatagramPacket
Java 通过 DatagramSocket
和 DatagramPacket
实现对 UDP 协议的支持。
DatagramPacket
:数据包类DatagramSocket
:通信类
UDP 服务端示例:
public class UDPServer {
public static void main(String[] args) throws Exception { // 所有异常抛出
String str = "hello World!!!";
DatagramSocket ds = new DatagramSocket(3000); // 服务端在 3000 端口上等待服务器发送信息
DatagramPacket dp =
new DatagramPacket(str.getBytes(), str.length(), InetAddress.getByName("localhost"), 9000); // 所有的信息使用 buf 保存
System.out.println("发送信息。");
ds.send(dp); // 发送信息出去
ds.close();
}
}
UDP 客户端示例:
public class UDPClient {
public static void main(String[] args) throws Exception { // 所有异常抛出
byte[] buf = new byte[1024]; // 开辟空间,以接收数据
DatagramSocket ds = new DatagramSocket(9000); // 客户端在 9000 端口上等待服务器发送信息
DatagramPacket dp = new DatagramPacket(buf, 1024); // 所有的信息使用 buf 保存
ds.receive(dp); // 接收数据
String str = new String(dp.getData(), 0, dp.getLength()) + "from " + dp.getAddress().getHostAddress() + ":"
+ dp.getPort();
System.out.println(str); // 输出内容
}
}
InetAddress
InetAddress
类表示互联网协议 (IP) 地址。
没有公有的构造函数,只能通过静态方法来创建实例。
InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);
URL
可以直接从 URL 中读取字节流数据。
public static void main(String[] args) throws IOException {
URL url = new URL("http://www.baidu.com");
/* 字节流 */
InputStream is = url.openStream();
/* 字符流 */
InputStreamReader isr = new InputStreamReader(is, "utf-8");
/* 提供缓存功能 */
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}