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
用于从源头(通常是文件)读取数据(字节信息)到内存中,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
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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 { File f = new File(filepath);
OutputStream out = new FileOutputStream(f);
String str = "Hello World\n"; byte[] bytes = str.getBytes(); out.write(bytes);
out.close(); }
public static void read(String filepath) throws IOException { File f = new File(filepath);
InputStream input = new FileInputStream(f);
byte[] bytes = new byte[(int) f.length()]; int len = input.read(bytes); System.out.println("读入数据的长度:" + len);
input.close(); System.out.println("内容为:\n" + new String(bytes)); }
}
|
内存字节流
ByteArrayInputStream
和 ByteArrayOutputStream
是用来完成内存的输入和输出功能。
内存操作流一般在生成一些临时信息时才使用。 如果临时信息保存在文件中,还需要在有效期过后删除文件,这样比较麻烦。
ByteArrayInputStream
和 ByteArrayOutputStream
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class ByteArrayStreamDemo {
public static void main(String[] args) { String str = "HELLOWORLD"; ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int temp = 0; while ((temp = bis.read()) != -1) { char c = (char) temp; bos.write(Character.toLowerCase(c)); } String newStr = bos.toString(); try { bis.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println(newStr); }
}
|
管道流
管道流的主要作用是可以进行两个线程间的通信。
如果要进行管道通信,则必须把 PipedOutputStream
连接在 PipedInputStream
上。为此,PipedOutputStream
中提供了 connect()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| 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
格式化读写数据示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| 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 { File f = new File(filepath);
DataOutputStream dos = new DataOutputStream(new FileOutputStream(f));
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'); }
dos.close(); }
private static void read(String filepath) throws IOException { File f = new File(filepath);
DataInputStream dis = new DataInputStream(new FileInputStream(f));
String name = null; float price = 0.0f; int num = 0; char[] temp = null; int len = 0; char c = 0; try { while (true) { temp = new char[200]; len = 0; while ((c = dis.readChar()) != '\t') { temp[len] = c; len++; } name = new String(temp, 0, len); price = dis.readFloat(); dis.readChar(); num = dis.readInt(); dis.readChar(); System.out.printf("名称:%s;价格:%5.2f;数量:%d\n", name, price, num); } } catch (EOFException e) { System.out.println("结束"); } catch (IOException e) { e.printStackTrace(); }
dis.close(); }
}
|
合并流
合并流的主要功能是将多个 InputStream
合并为一个 InputStream
流。合并流的功能由 SequenceInputStream
完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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(); is2.close(); os.close(); }
}
|
字符流
字符流主要操作字符,一般用于处理文本数据。
字符流有两个核心类:Reader
类和 Writer
。所有的字符流类都继承自这两个抽象类。
文件字符流
文件字符流 FileReader
和 FileWriter
可以向文件读写文本数据。
FileReader
和 FileWriter
读写文件示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| 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 { File f = new File(filepath);
Writer out = new FileWriter(f);
String str = "Hello World!!!\r\n"; out.write(str);
out.flush(); out.close(); }
public static char[] read(String filepath) throws IOException { File f = new File(filepath);
Reader input = new FileReader(f);
int temp = 0; int len = 0; char[] c = new char[1024]; while ((temp = input.read()) != -1) { c[len] = (char) temp; len++; } System.out.println("文件字符数为:" + len);
input.close();
return c; }
}
|
字节流转换字符流
我们可以在程序中通过 InputStream
和 Reader
从数据源中读取数据,然后也可以在程序中将数据通过 OutputStream
和 Writer
输出到目标媒介中
使用 InputStreamReader
可以将输入字节流转化为输入字符流;使用OutputStreamWriter
可以将输出字节流转化为输出字符流。
OutputStreamWriter
示例:
1 2 3 4 5 6 7 8 9 10
| 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
示例:
1 2 3 4 5 6 7 8 9 10 11 12
| 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
静态常量,可以根据所在操作系统选取对应的分隔符。
【示例】创建文件
1 2
| File f = new File(filename); boolean flag = f.createNewFile();
|
mkdir
可以使用 mkdir()
来创建文件夹,但是如果要创建的目录的父路径不存在,则无法创建成功。
如果要解决这个问题,可以使用 mkdirs()
,当父路径不存在时,会连同上级目录都一并创建。
【示例】创建目录
1 2
| File f = new File(filename); boolean flag = f.mkdir();
|
delete
可以使用 delete()
来删除文件或目录。
需要注意的是,如果删除的是目录,且目录不为空,直接用 delete()
删除会失败。
【示例】删除文件或目录
1 2
| File f = new File(filename); boolean flag = f.delete();
|
list 和 listFiles
File
中给出了两种列出文件夹内容的方法:
list()
: 列出全部名称,返回一个字符串数组。
listFiles()
: 列出完整的路径,返回一个 File
对象数组。
list()
示例:
1 2
| File f = new File(filename); String str[] = f.list();
|
listFiles()
示例:
1 2
| File f = new File(filename); File files[] = f.listFiles();
|
RandomAccessFile
注:RandomAccessFile
类虽然可以实现对文件内容的读写操作,但是比较复杂。所以一般操作文件内容往往会使用字节流或字符流方式。
RandomAccessFile
类是随机读取类,它是一个完全独立的类。
它适用于由大小已知的记录组成的文件,所以我们可以使用 seek()
将记录从一处转移到另一处,然后读取或者修改记录。
文件中记录的大小不一定都相同,只要能够确定哪些记录有多大以及它们在文件中的位置即可。
RandomAccessFile 写操作
当用 rw
方式声明 RandomAccessFile
对象时,如果要写入的文件不存在,系统将自行创建。
r
为只读;w
为只写;rw
为读写。
【示例】文件随机读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class RandomAccessFileDemo01 {
public static void main(String args[]) throws IOException { File f = new File("d:" + File.separator + "test.txt"); RandomAccessFile rdf = null; rdf = new RandomAccessFile(f, "rw"); String name = null; int age = 0; name = "zhangsan"; age = 30; rdf.writeBytes(name); rdf.writeInt(age); name = "lisi "; age = 31; rdf.writeBytes(name); rdf.writeInt(age); name = "wangwu "; age = 32; rdf.writeBytes(name); rdf.writeInt(age); rdf.close(); } }
|
RandomAccessFile 读操作
读取是直接使用 r
的模式即可,以只读的方式打开文件。
读取时所有的字符串只能按照 byte 数组方式读取出来,而且长度必须和写入时的固定大小相匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class RandomAccessFileDemo02 {
public static void main(String args[]) throws IOException { File f = new File("d:" + File.separator + "test.txt"); RandomAccessFile rdf = null; rdf = new RandomAccessFile(f, "r"); String name = null; int age = 0; byte b[] = new byte[8]; rdf.skipBytes(12); for (int i = 0; i < b.length; i++) { b[i] = rdf.readByte(); } name = new String(b); 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); 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); 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
输出流
1 2 3 4 5 6 7 8 9 10 11 12
| 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
输出流
1 2 3 4 5 6 7 8 9 10
| 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
接受控制台输入信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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
可以获取用户的输入,并对数据进行校验。
【示例】校验输入数据是否格式正确
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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); } }
|
输出:
1 2 3 4 5 6 7
| 输入整数: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 通信示例
服务端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class HelloServer {
public static void main(String[] args) throws Exception { 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("服务器已向客户端发送消息,退出。"); }
}
|
客户端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class HelloClient {
public static void main(String[] args) throws Exception { 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 服务端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class UDPServer {
public static void main(String[] args) throws Exception { String str = "hello World!!!"; DatagramSocket ds = new DatagramSocket(3000); DatagramPacket dp = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getByName("localhost"), 9000); System.out.println("发送信息。"); ds.send(dp); ds.close(); }
}
|
UDP 客户端示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class UDPClient {
public static void main(String[] args) throws Exception { byte[] buf = new byte[1024]; DatagramSocket ds = new DatagramSocket(9000); DatagramPacket dp = new DatagramPacket(buf, 1024); 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) 地址。
没有公有的构造函数,只能通过静态方法来创建实例。
1 2
| InetAddress.getByName(String host); InetAddress.getByAddress(byte[] address);
|
URL
可以直接从 URL 中读取字节流数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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(); }
|
参考资料