系列文章目录
文章目录
4、节点流之二:FileInputStream\FileOutputStream
FileInputStream 与 FileOutputStream
InputStreamReader 与 OutputStreamWriter
前言
本笔记为B站尚硅谷Java入门视频教程(在线答疑+Java面试真题)的笔记,是笔者对于已经学过的大二Java课程的查缺补漏及巩固。部分C语言学过的基本语法将跳过。
十六、File 类与 IO 流
1、java.io.File类的使用
构造器
•
public File(String pathname)
:以 pathname 为路径创建 File 对象,可以是绝对路径或者相对路径,如果 pathname 是相对路径,则默认的当前路径在系统属性user.dir 中存储。
•
public File(String parent, String child)
:以 parent 为父路径,child 为 子路径创建 File 对象。
•
public File(File parent, String child)
:根据一个父 File 对象和子文件 路径创建 File 对象
注意:
1、 无论该路径下是否存在文件或者目录,都不影响 File 对象的创建。
2、 window 的路径分隔符使用“\”,而 Java 程序中的“\”表示转义字符,所以在 Windows 中表示路径,需要用“\”。或者直接使用“/”也可 以,Java 程序支持将“/”当成平台无关的路径分隔符
。或者直接使用 File.separator 常量值表示。比如: File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
3、当构造路径是绝对路径时,那么 getPath 和 getAbsolutePath 结果一 样当构造路径是相对路径时,那么 getAbsolutePath 的路径 = user.dir 的路径 + 构造路1
常用方法
获取文件和目录基本信息
• public String getName() :获取名称
• public String getPath() :获取路径
•
public String getAbsolutePath()
:获取绝对路径
• public File getAbsoluteFile():获取绝对路径表示的文件
•
public String getParent()
:获取上层文件目录路径。若无,返回 null
• public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
• public long lastModified() :获取最后一次的修改时间,毫秒值
如果 File 对象代表的文件或目录存在,则 File 对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(例如,时间、类型等)为File 对象的属性赋值,否则除了路径和名称,File 对象的其他属性将会保留默认值。
@Test
public void test1(){
File file = new File("e:/test/111.txt");
System.out.println("文件构造路径:"+file.getPath());
System.out.println("文件名称:"+file.getName());
System.out.println("文件长度:"+file.length()+"字节");
System.out.println("文件最后修改时间:" + file.lastModified());
File f2 = new File("e:/test");
System.out.println("目录构造路径:"+f2.getPath());
System.out.println("目录名称:"+f2.getName());
System.out.println("目录长度:"+f2.length()+"字节");
System.out.println("文件最后修改时间:" + file.lastModified());
}
列出目录的下一级
• public String[] list() :返回一个 String 数组,表示该 File 目录中的所有子文件或目 录。
• public File[] listFiles() :返回一个 File 数组,表示该 File 目录中的所有的子文件或目 录。
@Test
public void test2(){
File dir = new File("d:/software");
String[] subs = dir.list();
for(String sub : subs){
System.out.println(sub);
}
System.out.println("_______________________________");
File dir1 = new File("d:/software");
File[] libs = dir1.listFiles();
for(File file : libs){
System.out.println(file);
}
File 类的重命名功能
• public boolean renameTo(File dest):把文件重命名为指定的文件路径。
判断功能的方法
•
public boolean exists()
:此 File 表示的文件或目录是否实际存在。
•
public boolean isDirectory()
:此 File 表示的是否为目录。
•
public boolean isFile()
:此 File 表示的是否为文件。
• public boolean canRead() :判断是否可读
• public boolean canWrite() :判断是否可写
• public boolean isHidden() :判断是否隐藏
创建、删除功能
•
public boolean createNewFile()
:创建文件。若文件存在,则不创建,返回 false。
•
public boolean mkdir()
:创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。
•
public boolean mkdirs()
:创建文件目录。如果上层文件目录不存在,一并创 建。
•
public boolean delete()
:删除文件或者文件夹 删除注意事项:
① Java 中的 删除不走回收站。
② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。
2、IO 流原理及流的分类
Java IO 原理
• I/O 流中的 I/O 是 Input/Output
的缩写, I/O 技术是非常实用的技术,用于处理设 备之间的数据传输。如读/写文件,网络通讯等。
– 输入
input
:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内 存)中。
– 输出
output
:将程序(内存)数据输出到磁盘、光盘等存储设备中。
流的分类
java.io
包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过
标准
的方法
输入或输出数据。
• 按数据的流向不同分为:输入流
和
输出流
。
– 输入流
:把数据从
其他设备
上读取到
内存
中的流。
• 以 InputStream、Reader 结尾
– 输出流
:把数据从
内存
中写出到
其他设备
上的流。
• 以 OutputStream、Writer 结尾
• 按操作数据单位的不同分为:字节流(8bit)
和
字符流(16bit)
。
– 字节流
:以字节为单位,读写数据的流。
• 以 InputStream、OutputStream 结尾
– 字符流
:以字符为单位,读写数据的流。
• 以 Reader、Writer 结尾
• 根据 IO 流的角色不同分为:节点流
和
处理流
。
– 节点流
:直接从数据源或目的地读写数据
– 处理流
:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点 流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
流的 API
• Java 的 IO 流共涉及 40 多个类,实际上非常规则,都是从如下 4 个抽象基类派生的。
(抽象基类)
| 输入流 | 输出流 |
字节流 |
InputStream
|
OutputStream
|
字符流 |
Reader
|
Writer
|
• 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
常用的节点流:
• 文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
• 字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、 CharArrayReader、CharArrayWriter
– 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
常用处理流:
• 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、 BufferedWriter
– 作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率。
• 转换流:InputStreamReader、OutputStreamReader
– 作用:实现字节流和字符流之间的转换。
• 对象流:ObjectInputStream、ObjectOutputStream
– 作用:提供直接读写 Java 对象功能
3、节点流之一:FileReader\FileWriter
Reader 与 Writer
Java 提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。不能操作图片,视频等非文本文件。常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py 等
注意:.doc、.xls、.ppt 这些都不是文本文件。
字符输入流:Reader
java.io.Reader
抽象类是表示用于读取字符流的所有类的父类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
•
public int read()
: 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为 int 类型。返回该字符的 Unicode 编码值。如果已经到达流末尾了,则返回- 1。
•public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中 。每次最多读取 cbuf.length 个字符。返回实际读取的字符个数。 如果已经到达流末尾,没有数据可读,则返回-1。
•public int read(char[] cbuf,int off,int len):从输入流中读取一些字符,并将它们存储到字符数组 cbuf 中,从 cbuf[off]开始的位置存储。每次最多读取len 个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。
•public void close() :关闭此流并释放与此流相关联的任何系统资源。
注意:当完成流的操作时,必须调用 close()方法,释放系统资源,否则会造成内存泄漏。
字符输出流:Writer
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
•public void write(int c) :写出单个字符。
•public void write(char[] cbuf):写出字符数组。
•public void write(char[] cbuf, int off, int len):写出字符数组的某 一部分。off:数组的开始索引;len:写出的字符个数。
•public void write(String str):写出字符串。
•public void write(String str, int off, int len) :写出字符串的某一部分。off:字符串的开始索引;len:写出的字符个数。
•
public void flush()
:刷新该流的缓冲。
•
public void close()
:关闭此流。
注意:当完成流的操作时,必须调用 close()方法,释放系统资源,否则会造成内存泄漏。
FileReader 与 FileWriter
FileReader
java.io.FileReader
类用于读取字符文件,构造时使用系统默认的字符编码和默认字节缓冲区。
•FileReader(File file): 创建一个新的 FileReader ,给定要读取的 File 对象。
•FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。
//实现方式 1
@Test
public void test1() throws IOException {
File file = new File("E:/test/111.txt");
FileReader fr = new FileReader(file);
int data;
while((data = fr.read()) != -1){
System.out.println((char)data);
}
fr.close();
}
//实现方式 2:在方式 1 的基础上改进,使用 try-catch-finally 处理异常。保证流是可以关闭的
@Test
public void test2(){
FileReader fr = null;
try{
File file = new File("E:/test/111.txt");
fr = new FileReader(file);
int data;
while ((data = fr.read()) != -1){
System.out.println((char)data);
}
}catch (IOException e){
e.printStackTrace();
}finally {
try {
if(fr != null){
fr.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
//实现方式 3:调用 read(char[] cbuf),每次从文件中读取多个字符
@Test
public void test3(){
FileReader fr = null;
try {
File file = new File("E:/test/111.txt");
fr = new FileReader(file);
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1){
String str = new String(cbuf, 0 ,len);
System.out.println(str);
}
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(fr != null)
fr.close();
}catch (IOException e){
e.printStackTrace();
}
}
FileWriter
java.io.FileWriter
类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区。
•
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的 File 对象。
•
FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件 的名称。
•
FileWriter(File file,boolean append)
: 创建一个新的 FileWriter,指明是 否在现有文件末尾追加内容。
@Test
public void test4() {
FileWriter fw = null;
try {
fw = new FileWriter(new File("fw.txt"));
fw.write(97);
fw.write('b');
fw.write('C');
fw.write('3');
fw.write(30000); // 中文编码表中 30000 对应一个汉字。
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test5(){
FileWriter fw = null;
try {
fw = new FileWriter(new File("fw.txt"));
// 字符串转换为字节数组
char[] chars = "嗨你好呀".toCharArray();
fw.write(chars);
fw.write(chars, 1, 2);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test6(){
FileWriter fw = null;
try {
fw = new FileWriter(new File("fw.txt"));
String msg = "欢迎光临";
fw.write(msg);
fw.write(msg,1,2);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test7(){
FileWriter fw = null;
try{
File file = new File("fw.txt");
//如果输出的文件已存在,则会对现有的文件进行覆盖
//fw = new FileWriter(file);
//fw = new FileWriter(file,false);
//如果输出的文件已存在,则会在现有的文件末尾写入数据
fw = new FileWriter(file,true);
fw.write("I love you,");
fw.write("you love him.");
fw.write("so sad".toCharArray());
}catch (IOException e){
e.printStackTrace();
}finally {
try {
if(fw != null){
fw.close();
}
}catch (IOException e){
throw new RuntimeException(e);
}
}
}
小结
① 因为出现流资源的调用,为了避免内存泄漏,需要使用 try-catch-finally
处理异 常
②对于输入流来说,File
类的对象必须在物理磁盘上存在,否则执行就会报 FileNotFoundException。如果传入的是一个目录,则会报 IOException
异常。
③对于输出流来说,File
类的对象是可以不存在的。
> 如果
File
类的对象不存在,则可以在输出的过程中,自动创建
File
类的对象
> 如果
File
类的对象存在:
* 如果调用 FileWriter(File file)
或
FileWriter(File file,false)
,输出时会新建 File
文件覆盖已有的文件
* 如果调用 FileWriter(File file,true)
构造器,则在现有的文件末尾追加写出内容
关于 flush(刷新)
因为内置缓冲区的原因,如果 FileWriter 不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要 flush()
方法了。
•flush() :刷新缓冲区,流对象可以继续使用。
•close():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
注意:即便是 flush()方法写出了数据,操作的最后还是要调用 close 方法,释放系统资源
@Test
public void test1() throws IOException {
FileWriter fw = new FileWriter("f.txt");
fw.write("哇");
fw.write("真");
fw.write("的");
fw.write("是");
fw.flush();
fw.write("你");
fw.write("啊");
// fw.close();
}
4、节点流之二:FileInputStream\FileOutputStream
如果我们读取或写出的数据是非文本文件,则 Reader、Writer 就无能为力了, 必须使用字节流。
InputStream 和 OutputStream
字节输入流:InputStream
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
•
public int read()
: 从输入流读取一个字节。返回读取的字节值。虽然读取了 一个字节,但是会自动提升为 int 类型。如果已经到达流末尾,没有数据可读,则返 回-1。
•public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b 中 。每次最多读取 b.length 个字节。返回实际读取的字节个数。如果已经 到达流末尾,没有数据可读,则返回-1。
•public int read(byte[] b,int off,int len):从输入流中读取一些字节数,并将它们存储到字节数组 b 中,从 b[off]开始存储,每次最多读取 len 个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
•public void close() :关闭此输入流并释放与此流相关联的任何系统资源。 说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。
字节输出流:OutputStream
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
•public void write(int b) :将指定的字节输出流。虽然参数为 int 类型四个字节,但是只会保留一个字节的信息写出。
•public void write(byte[] b):将 b.length 字节从指定的字节数组写入此输出流。
•public void write(byte[] b, int off, int len) :从指定的字节数组写入 len 字节,从偏移量 off 开始输出到此输出流。
•public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
•public void close() :关闭此输出流并释放与此流相关联的任何系统资源。 说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileInputStream 与 FileOutputStream
FileInputStream
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
•FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File 对象 file 命名。
•FileInputStream(String name): 通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名 name 命名。
FileOutputStream
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
•public FileOutputStream(File file):创建文件输出流,写出由指定的 File 对象表示的文件。
•public FileOutputStream(String name): 创建文件输出流,指定的名称为写出文件。
•public FileOutputStream(File file, boolean append): 创建文件输出流,指明是否在现有文件末尾追加内容。
使用
FileInputStream\FileOutputStream
,实现对文件的复制
@Test
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("jmu_sd_1.jpg");
fos = new FileOutputStream("jmu_sd_1_new.jpg");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("复制成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis != null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fos != null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
5、处理流之一:缓冲流
•为了提高数据读写的速度,Java API 提供了带缓冲功能的流类:缓冲流。
•缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
–字节缓冲流:BufferedInputStream
,
BufferedOutputStream
–字符缓冲流:BufferedReader
,
BufferedWriter
•缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用 8192
个字节
(8Kb)
的缓冲区),通过缓冲区读写,减少系统 IO 次数,从而提高读写的效率。
构造器
•
public BufferedInputStream(InputStream in)
:创建一个 新的字节型的缓冲输入流。
•public BufferedOutputStream(OutputStream out): 创建一个新的字节型的缓冲输出流。
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.jpg"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc_copy.jpg"));
•public BufferedReader(Reader in) :创建一个新的字符型的缓冲输入流。
•public BufferedWriter(Writer out): 创建一个新的字符型的缓冲输出流。
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
字符缓冲流特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
• BufferedReader:public String readLine()
: 读一行文字。
•BufferedWriter:public void newLine()
: 写一行行分隔符,由系统属性定义符号。
@Test
public void testReadLine() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("f.txt"));
String line;
// 循环读取,读取到最后返回 null
while ((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
@Test
public void testNewLine() throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("f.txt"));
bw.write("你");
bw.newLine();
bw.write("好");
bw.close();
}
说明:
涉及到嵌套的多个流时,如果都显式关闭的话,需要先关闭外层的 流,再关闭内层的流
其实在开发中,只需要关闭最外层的流即可,因为在关闭外层流时,内层的流也会被关闭。
6、处理流之二:转换流
问题引入
使用
FileReader
读取项目中的文本文件。由于 IDEA 设置中针对项目设置了 UTF-8 编码,当读取 Windows 系统中创建的文本文件时,如果 Windows 系统默认的是 GBK 编码,则读入内存中会出现乱码。
那么如何读取 GBK 编码的文件呢?
转换流的理解
作用:转换流是字节与字符间的桥梁!
InputStreamReader 与 OutputStreamWriter
•InputStreamReader
–转换流 java.io.InputStreamReader,是 Reader 的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
–构造器
•InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
•InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
@Test
public void test1() throws IOException {
// 定义文件路径,文件为 gbk 编码
String fileName = "E://test/11.txt";
//方式 1:
// 创建流对象,默认 UTF8 编码
InputStreamReader isr1 = new InputStreamReader(new FileInputStream(fileName));
// 定义变量,保存字符
int charData;
// 使用默认编码字符流读取,乱码
while ((charData = isr1.read()) != -1) {
System.out.print((char)charData); // ��Һ�
}
isr1.close();
//方式 2:
// 创建流对象,指定 GBK 编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(fileName) , "GBK");
// 使用指定编码字符流读取,正常解析
while ((charData = isr2.read()) != -1) {
System.out.print((char)charData);// 大家好
}
isr2.close();
}
•
OutputStreamWriter
–转换流 java.io.OutputStreamWriter ,是 Writer 的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
–构造器
•OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
• OutputStreamWriter(OutputStream in,String charsetName)
: 创建一个指定字符集的字符流。
7、处理流之三/四:数据流、对象流
数据流与对象流说明
如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文件中,那怎么办呢?
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
String name = "巫师";
Student stu = new Student("张三",23,89);
Java 提供了数据流和对象流来处理这些类型的数据:
•数据流:DataOutputStream、DataInputStream
–DataOutputStream:允许应用程序将基本数据类型、String 类型的变量写入输出流中
–DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String 类型的变量。
•对象流 DataInputStream 中的方法:
byte
readByte
()
short
readShort
()
int
readInt
()
long
readLong
()
float
readFloat
()
double
readDouble
()
char
readChar
()
boolean
readBoolean
()
String
readUTF
()
void
readFully
(
byte
[]
b
)
• 对象流 DataOutputStream 中的方法:将上述的方法的 read 改为相应的 write 即可。
•数据流的弊端:只支持 Java 基本数据类型和字符串的读写,而不支持其它 Java 对象的类型。而 ObjectOutputStream 和 ObjectInputStream 既支持 Java 基本数据类型的数据读写,又支持 Java 对象的读写,所以重点介绍对象流 ObjectOutputStream 和 ObjectInputStream。
•
对象流:ObjectOutputStream、ObjectInputStream
–ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。 通过在流中使用文件可以实现 Java 各种基本数据类型的数据以及对象的持久存储。
–ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写出的基本数据类型的数据和对象进行读入操作,保存在内存中。
说明:对象流的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。
对象流 API
ObjectOutputStream 中的构造器:
public ObjectOutputStream(OutputStream out): 创建一个指定的 ObjectOutputStream。
FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
ObjectOutputStream 中的方法:
•public void writeBoolean(boolean val):写出一个 boolean 值。
•public void writeByte(int val):写出一个 8 位字节
•public void writeShort(int val):写出一个 16 位的 short 值
•public void writeChar(int val):写出一个 16 位的 char 值
•public void writeInt(int val):写出一个 32 位的 int 值
•public void writeLong(long val):写出一个 64 位的 long 值
•public void writeFloat(float val):写出一个 32 位的 float 值。
•public void writeDouble(double val):写出一个 64 位的 double 值
•public void writeUTF(String str):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。根据字符的值,将字符串 s 中每个字符转 换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入 流中与将它作为 Object 写入流中明显不同。 如果 s 为 null,则抛出 NullPointerException。
•public void writeObject(Object obj):写出一个 obj 对象
•public void close() :关闭此输出流并释放与此流相关联的任何系统资源
ObjectInputStream 中的构造器:
public ObjectInputStream(InputStream in)
: 创建一个指定的 ObjectInputStream。
ObjectInputStream 中的方法:
•public boolean readBoolean():读取一个 boolean 值
•public byte readByte():读取一个 8 位的字节
•public short readShort():读取一个 16 位的 short 值
•public char readChar():读取一个 16 位的 char 值
•public int readInt():读取一个 32 位的 int 值
•public long readLong():读取一个 64 位的 long 值
•public float readFloat():读取一个 32 位的 float 值
•public double readDouble():读取一个 64 位的 double 值
•public String readUTF():读取 UTF-8 修改版格式的 String
•public void readObject(Object obj):读入一个 obj 对象
•public void close() :关闭此输入流并释放与此流相关联的任何系统资源
@Test
public void test1() throws IOException {
String name = "巫师";
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat"));
oos.writeUTF(name);
oos.writeInt(age);
oos.writeChar(gender);
oos.writeInt(energy);
oos.writeDouble(price);
oos.writeBoolean(relive);
oos.close();
}
@Test
public void test2() throws IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat"));
String name = ois.readUTF();
int age = ois.readInt();
char gender = ois.readChar();
int energy = ois.readInt();
double price = ois.readDouble();
boolean relive = ois.readBoolean();
System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive);
ois.close();
}
认识对象序列化机制
对象序列化机制
允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
(当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象。)
•序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型
和
对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中
持久保存
了一个对象的信息。
•反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列
化
。
对象的数据
、
对象的类型
和
对象中存储的数据
信息,都可以用来在内存中创建 对象。
序列化机制的重要性
序列化是 RMI(Remote Method Invoke、远程方法调用)过程的参数和返回值 都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。
序列化的好处,在于可将任何实现了 Serializable 接口的对象转化为
字节数据
,使其在保存和传输时可被还原。
如何实现序列化机制
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现 java.io.Serializable 接口。Serializable
是一个
标记接口
,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException
。
• 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现
Serializable
接口
• 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必 须注明是瞬态的,使用 transient
关键字修饰。
•
静态(
static
)变量
的值不会序列化。因为静态变量的值不属于某个对象。
public class Employee implements Serializable {
//static final long serialVersionUID = 23234234234L;
public static String company; //static 修饰的类变量,不会被序列化
public String name;
public String address;
public transient int age; // transient 瞬态修饰成员,不会被序列化
public Employee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
Employee.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
", company=" + company +
'}';
}
}
@Test
public void save() throws IOException {
Employee.setCompany("尚硅谷");
Employee e = new Employee("小谷姐姐", "宏福苑", 23);
// 创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
oos.writeObject(e);
oos.close();
System.out.println("Serialized data is saved");// 姓名,地址被序列化,年龄没有被序列化
}
@Test
public void reload() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("employee.dat"));
Employee e = (Employee) ois.readObject();
ois.close();
System.out.println(e);
}
@Test
public void save2() throws IOException {
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", "宏福苑", 23));
list.add(new Employee("李四", "白庙", 24));
list.add(new Employee("王五", "平西府", 25));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
oos.writeObject(list);
oos.close();
}
@Test
public void reload2() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("employee.dat"));
ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject();
ois.close();
System.out.println(list);
}
反序列化失败问题
问题 1:
对于 JVM 可以反序列化对象,它必须是能够找到 class 文件的类。如果找不到该类的 class 文件,则抛出一个 ClassNotFoundException
异常。
问题 2:
当 JVM 反序列化对象时,能找到 class 文件,但是 class 文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个 InvalidClassException 异常。发生这个异常的原因如下:
•该类的序列版本号与从流中读取的类描述符的版本号不匹配
•该类包含未知数据类型
解决办法:
Serializable
接口给需要序列化的类,提供了一个序列版本号:
serialVersionUID 。凡是实现 Serializable 接口的类都应该有一个表示序列化版本标识符的静态变量:
static final long serialVersionUID = 234242343243L; //它的值由程序员随意指定即可。
• serialVersionUID 用来表明类的不同版本间的兼容性。简单来说,Java 的序列化机制是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。
• 如果类没有显示定义这个静态常量,它的值是 Java 运行时环境根据类的内部细节自
动生成
的。若类的实例变量做了修改,serialVersionUID
可能发生变化
。因此,建议 显式声明。
• 如果声明了 serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则 原来的数据也能正常反序列化,只是新增的字段值是默认值而已。
8、其他流的使用
标准输入、输出流
• System.in 和 System.out 分别代表了系统标准的输入和输出设备
• 默认输入设备是:键盘,输出设备是:显示器
• System.in 的类型是 InputStream
• System.out 的类型是 PrintStream,其是 OutputStream 的子类 FilterOutputStream 的 子类
• 重定向:通过 System 类的 setIn,setOut 方法对默认设备进行改变。
– public static void setIn(InputStream in)
– public static void setOut(PrintStream out)
public class InOutTest {
//从键盘输入字符串,要求将读取到的整行字符串转成大写输出。
// 然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
public static void main(String[] args) {
System.out.println("请输入信息(退出输入 e 或 exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = null;
try {
while((s = br.readLine()) != null){
if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
System.out.println("安全退出!!");
break;
}
// 将读取到的整行字符串转成大写输出
System.out.println("-->:" + s.toUpperCase());
System.out.println("继续输入信息");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
打印流
• 实现将基本数据类型的数据格式转化为字符串输出。
• 打印流:PrintStream
和
PrintWriter
– 提供了一系列重载的 print()和 println()方法,用于多种数据类型的输出
– PrintStream 和 PrintWriter 的输出不会抛出 IOException 异常
– PrintStream 和 PrintWriter 有自动 flush 功能
– PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
– System.out 返回的是 PrintStream 的实例
• 构造器
– PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
– PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
– PrintStream(OutputStream out) :创建新的打印流。
– PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。 autoFlush 如果为 true,则每当写入 byte 数组、调用其中一个 println 方 法或写入换行符或字节 ('\n') 时都会刷新输出缓冲区。
– PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建 新的打印流。
– PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的 新打印流。
– PrintStream(String fileName, String csn) :创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
public class TestPrintStream {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("io.txt");
ps.println("hello");
ps.println(1);
ps.println(1.5);
ps.close();
}
}
public class TestPrintStream {
public static void main(String[] args) {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(new File("io.txt"));
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
ps = new PrintStream(fos, true);
if (ps != null) { // 把标准输出流(控制台输出)改成文件
System.setOut(ps);
}
for (int i = 0; i <= 255; i++) { // 输出 ASCII 字符
System.out.print((char) i);
if (i % 50 == 0) { // 每 50 个数据一行
System.out.println(); // 换行
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
}
}
}
Scanner 类
构造方法:
• Scanner(File source) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
• Scanner(File source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
• Scanner(InputStream source) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
• Scanner(InputStream source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
常用方法:
• boolean hasNextXxx(): 如果通过使用 nextXxx()方法,此扫描器输入信息中的下一个标记可以解释为默认基数中的一个 Xxx 值,则返回 true。
• Xxx nextXxx(): 将输入信息的下一个标记扫描为一个 Xxx
public class TestScanner {
public static void main(String[] args) throws FileNotFoundException {
Scanner input = new Scanner(System.in);
PrintStream ps = new PrintStream("io.txt");
while(true){
System.out.print("请输入一个单词:");
String str = input.nextLine();
if("stop".equals(str)){
break;
}
ps.println(str);
}
input.close();
ps.close();
}
}
public class TestScanner {
public static void main(String[] args) throws FileNotFoundException {
Scanner input = new Scanner(new FileInputStream("io.txt"));
while (input.hasNextLine()){
String str = input.nextLine();
System.out.println(str);
}
input.close();
}
}
十七、网络编程
1、网络编程概述
Java 提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的 网络库,程序员面对的是一个统一的网络编程环境
。
软件架构
C/S 架构
:全称为 Client/Server 结构,是指客户端和服务器结构。常见程序有 QQ、 美团 app、360 安全卫士等软件。
B/S 架构
:全称为 Browser/Server 结构,是指浏览器和服务器结构。常见浏览
器有 IE、谷歌、火狐等。
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。
网络编程
,就是在一定的协议下,实现两台计算机的通信的程序。
2、网络通信要素
如何实现网络中的主机互相通信
• 通信双方地址
– IP
– 端口号
• 一定的规则:不同的硬件、操作系统之间的通信,所有的这一切都需要一种规则。而我们就把这种规则称为协议,即网络通信协议。
通信要素一:IP 地址和域名
IP 地址
IP 地址:指互联网协议地址(Internet Protocol Address)
,俗称 IP。IP 地址用来给网络中的一台计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP 地址”就相当于“电话号码”。
IP 地址分类方式一:
•
IPv4
:是一个 32 位的二进制数,通常被分为 4 个字节,表示成
a.b.c.d
的形式, 以点分十进制
表示,例如
192.168.65.100
。其中 a、b、c、d 都是 0~255 之间的十进制整数。
– 这种方式最多可以表示 42 亿个。其中,30 亿都在北美,亚洲 4 亿,中国2.9 亿。2011 年初已经用尽。
– IP 地址 = 网络地址 +主机地址
• 网络地址:标识计算机或网络设备所在的网段
• 主机地址:标识特定主机或网络设备
•
IPv6
:由于互联网的蓬勃发展,IP 地址的需求量愈来愈大,但是网络地址资源有限,使得 IP 的分配越发紧张。
为了扩大地址空间,拟通过 IPv6 重新定义地址空间,采用 128 位地址长度,共 16 个字节,写成 8 个无符号整数,每个整数用四个十六进制位表示,数之间用冒号 (:)分开。比如:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
,按保守方 法估算 IPv6 实际可分配的地址,整个地球的每平方米面积上仍可分配 1000 多个地 址,这样就解决了网络地址资源数量不够的问题。2012 年 6 月 6 日,国际互联网 协会举行了世界 IPv6 启动纪念日,这一天,全球 IPv6 网络正式启动。多家知名网 站,如 Google、Facebook 和 Yahoo 等,于当天全球标准时间 0 点(北京时间 8 点 整)开始永久性支持 IPv6 访问。2018 年 6 月,三大运营商联合阿里云宣布,将全 面对外提供 IPv6 服务,并计划在 2025 年前助推中国互联网真正实现“IPv6 Only”。在 IPv6 的设计过程中除一劳永逸地解决了地址短缺问题以外,还考虑了在 IPv4 中解决不好的其它问题,主要有端到端 IP 连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。
IP 地址分类方式二:
公网地址( 万维网使用)和 私有地址( 局域网使用)。192.168.开头的就是私有地址,范围即为 192.168.0.0--192.168.255.255,专门为组织机构内部使用。
常用命令:
• 查看本机 IP 地址,在控制台输入:
ipconfig
• 检查网络是否连通,在控制台输入:
ping
空格
IP
地址
ping
220.181.57.216
特殊的 IP 地址:
• 本地回环地址(hostAddress):127.0.0.1
• 主机名(hostName):localhost
域名
Internet 上的主机有两种方式表示地址:
• 域名(hostName):www.atguigu.com
• IP 地址(hostAddress):202.108.35.210
域名解析:
因为 IP 地址数字不便于记忆,因此出现了域名。域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS,Domain Name System,域名系统)负责将域名转化成 IP 地址,这样才能和主机建立连接。
通信要素二:端口号
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说
IP 地址
可以唯一标识网络中的设备,那么
端口号
就可以唯一标识设备中的进程(应用程序)。
不同的进程,设置不同的端口号。
•
端口号:用两个字节表示的整数,它的取值范围是 0~65535
。
– 公认端口:0~1023。被预先定义的服务通信占用,如:HTTP(80),FTP(21),Telnet(23)
– 注册端口:1024~49151。分配给用户进程或应用程序。如:Tomcat(8080),MySQL(3306),Oracle(1521)。
– 动态/ 私有端口:49152~65535。
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
通信要素三:网络通信协议
•
网络通信协议
:在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤、出错控制等做了统一规定,通信双方必须同时遵守才能完成数据交换。
•
TCP/IP 协议:
传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互 联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。 是 Internet 最基本、最广泛的协议。
TCP/IP 协议中的四层介绍:
•
应用层
:应用层决定了向用户提供应用服务时通信的活动。主要协议有:HTTP 协议、FTP 协议、SNMP(简单网络管理协议)、SMTP(简单邮件传输协议)和 POP3 (Post Office Protocol 3 的简称,即邮局协议的第 3 个版)等。
•
传输层
:主要使网络程序进行通信,在进行网络通信时,可以采用 TCP 协议,也可 以采用 UDP 协议。TCP(Transmission Control Protocol)协议,即传输控制协议,是 一种面向连接的、可靠的、基于字节流的传输层通信协议。UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务。
•
网络层
:网络层是整个 TCP/IP 协议的核心,支持网间互连的数据通信。它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。而 IP 协议是一种 非常重要的协议。IP(internet protocal)又称为互联网协议。IP 的责任就是把数据从 源传送到目的地。它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。
•
物理
+
数据链路层
:链路层是用于定义物理传输通道,通常是对某些网络连接设备的 驱动协议,例如针对光纤、网线提供的驱动。
TCP 协议:
• TCP 协议进行通信的两个应用进程:客户端、服务端。
使用 TCP 协议前,须先建立
TCP
连接
,形成基于字节流的传输数据通道
• 传输前,采用“三次握手”方式,点对点通信,是可靠的
– TCP 协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息。 在连接中可进行大数据量的传输传输完毕,需释放已建立的连接,效率低
UDP 协议:
• UDP 协议进行通信的两个应用进程:发送端、接收端。
将数据、源、目的封装成数据包(传输的基本单位),不需要建立连接
发送不管对方是否准备好,接收方收到也不确认,不能保证数据的完整性,故是
不可靠的每个数据报的大小限制在 64K
内发送数据结束时无需释放资源,开销小,通信效率高
• 适用场景:音频、视频和普通数据的传输。例如视频会议
TCP 生活案例:打电话
UDP 生活案例:发送短信、发电报
3、网络编程 API
InetAddress 类
InetAddress 类主要表示 IP 地址,两个子类:Inet4Address、Inet6Address。 InetAddress 类没有提供公共的构造器,而是提供 了 如下几个 静态方法来获取 InetAddress 实例
• public static InetAddress getLocalHost()
• public static InetAddress getByName(String host)
• public static InetAddress getByAddress(byte[] addr)
InetAddress 提供了如下几个常用的方法:
• public String getHostAddress() :返回 IP 地址字符串(以文本表现形式)
• public String getHostName() :获取此 IP 地址的主机名
• public boolean isReachable(int timeout):测试是否可以达到该地址
@Test
public void test1() throws UnknownHostException {
InetAddress localhost = InetAddress.getLocalHost();
System.out.println(localhost);
}
@Test
public void test2() throws UnknownHostException {
InetAddress name = InetAddress.getByName("www.atguigu.com");
System.out.println(name);
}
@Test
public void test3() throws UnknownHostException {
byte[] addr = {(byte)192,(byte)168,24,56};
InetAddress atguigu = InetAddress.getByAddress(addr);
System.out.println(atguigu);
}
Socket 类
网络上具有唯一标识的 IP 地址和端口号组合在一起构成唯一能识别的标识符套接字(Socket)。
利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。网络通信其实就是 Socket 间的通信。
Socket 分类:
– 流套接字(stream socket):使用 TCP 提供可依赖的字节流服务
• ServerSocket:此类实现 TCP 服务器套接字。服务器套接字等待请求通过网络传入。
• Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
– 数据报套接字(datagram socket):使用 UDP 提供“尽力而为”的数据报服务
• DatagramSocket:此类表示用来发送和接收 UDP 数据报包的套接 字。
Socket 相关类 API
ServerSocket 类
ServerSocket 类的构造方法:
• ServerSocket(int port) :创建绑定到特定端口的服务器套接字。
ServerSocket 类的常用方法:
• Socket accept():侦听并接受到此套接字的连接。
Socket 类
Socket 类的常用构造方法
:
• public Socket(InetAddress address,int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
• public Socket(String host,int port):创建一个流套接字并将其连接到指定主机上的指 定端口号。
Socket 类的常用方法:
• public InputStream getInputStream():返回此套接字的输入流,可以用于接收消息
• public OutputStream getOutputStream():返回此套接字的输出流,可以用于发送消 息
• public InetAddress getInetAddress():此套接字连接到的远程 IP 地址;如果套接字是 未连接的,则返回 null。
• public InetAddress getLocalAddress():获取套接字绑定的本地地址。
• public int getPort():此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
• public int getLocalPort():返回此套接字绑定到的本地端口。如果尚未绑定套接字,则 返回 -1。
• public void close():关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使 用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会 关闭该套接字的 InputStream 和 OutputStream。
• public void shutdownInput():如果在套接字上调用 shutdownInput() 后从套接字输入 流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
• public void shutdownOutput():禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
注意:
先后调用 Socket 的 shutdownInput()和 shutdownOutput()方法,仅仅关闭了输入流和输出流,并不等于调用 Socket 的 close()方法。在通信结束后,仍然要调用 Scoket 的 close()方法,因为只有该方法才会释放 Socket 占用的资源,比如占用的本地端口号等。
DatagramSocket 类
DatagramSocket 类的常用方法:
• public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
• public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
• public void close()关闭此数据报套接字。
• public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
• public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和 发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
• public InetAddress getLocalAddress()获取套接字绑定的本地地址。
• public int getLocalPort()返回此套接字绑定的本地主机上的端口号。
• public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接, 则返回
null。
• public int getPort()返回此套接字的端口。如果套接字未连接,则返回 -1。
DatagramPacket 类
DatagramPacket 类的常用方法:
• public DatagramPacket(byte[] buf,int length)构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
• public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数据报 包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小 于等于 buf.length。
• public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器 或者是从该机器接收到的。
• public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者是从该 主机接收到的。
•
public byte[] getData()
返回数据缓冲区。接收到的或将要发送的数据从缓冲区 中的偏移量 offset 处开始,持续 length 长度。
•
public int getLength()
返回将要发送或接收到的数据的长度。
4、TCP 网络编程
通信模型
Java 语言的基于套接字 TCP 编程分为服务端编程和客户端编程,其通信模型如图所示:
开发步骤
客户端程序包含以下四个基本的步骤 :
• 创建 Socket :根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器 端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
• 打开连接到 Socket 的输入/ 出流: 使用 getInputStream()方法获得输入流,使用 getOutputStream()方法获得输出流,进行数据传输
• 按照一定的协议对 Socket 进行读/ 写操作:通过输入流读取服务器放入线路的信息 (但不能读取自己放入线路的信息),通过输出流将信息写入线路。
• 关闭 Socket :断开客户端到服务器的连接,释放线路
服务器端程序包含以下四个基本的 步骤:
• 调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
• 调用 accept() :监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
• 调用 该 Socket 类对象的 getOutputStream() 和 getInputStream () :获取输出流和输入流,开始网络数据的发送和接收。
• 关闭 Socket 对象:客户端访问结束,关闭通信套接字。
演示单个客户端与服务器单次通信:
public class Server {
public static void main(String[] args) throws IOException {
//1、准备一个 ServerSocket 对象,并绑定 8888 端口
ServerSocket server = new ServerSocket(8888);
System.out.println("等待连接、、、、");
//2、在 8888 端口监听客户端的连接,该方法是个阻塞的方法,如果没有客户端连接,将一直等待
Socket socket = server.accept();
InetAddress inetAddress = socket.getInetAddress();
System.out.println(inetAddress.getHostAddress() + "客户端连接成功!");
//3、获取输入流,用来接收该客户端发送给服务器的数据
InputStream input = socket.getInputStream();
//接收数据
byte[] data = new byte[1024];
StringBuilder s = new StringBuilder();
int len;
while ((len = input.read(data)) != -1){
s.append(new String(data, 0, len));
}
System.out.println(inetAddress.getHostAddress() + "客户端发送的消息是:" + s);
//4、获取输出流,用来发送数据给该客户端
OutputStream out = socket.getOutputStream();
//发送数据
out.write("欢迎登录".getBytes());
out.flush();
//5、关闭 socket,不再与该客户端通信
//socket 关闭,意味着 InputStream 和 OutputStream 也关闭了
socket.close();
//6、如果不再接收任何客户端通信,可以关闭 ServerSocket
server.close();
}
}
public class Client {
public static void main(String[] args) throws Exception{
// 1、准备 Socket,连接服务器,需要指定服务器的 IP 地址和端口号
Socket socket = new Socket("127.0.0.1",8888);
// 2、获取输出流,用来发送数据给服务器
OutputStream out = socket.getOutputStream();
// 发送数据
out.write("嘿嘿嘿".getBytes());
//会在流末尾写入一个“流的末尾”标记,对方才能读到-1,否则对方的读取方法会一致阻塞
socket.shutdownOutput();
//3、获取输入流,用来接收服务器发送给该客户端的数据
InputStream input = socket.getInputStream();
// 接收数据
byte[] data = new byte[1024];
StringBuilder s = new StringBuilder();
int len;
while ((len = input.read(data)) != -1){
s.append(new String(data, 0 ,len));
}
System.out.println("服务器返回的消息是:" + s);
//4、关闭 socket,不再与服务器通信,即断开与服务器的连接
//socket 关闭,意味着 InputStream 和 OutputStream 也关闭了
socket.close();
}
}
演示多个客户端与服务器之间的多次通信:
通常情况下,服务器不应该只接受一个客户端请求,而应该不断地接受来自客
户端的所有请求,所以 Java 程序通常会通过循环,不断地调用 ServerSocket 的
accept()方法。
如果服务器端要“同时”处理多个客户端的请求,因此服务器端需要为
每一个客
户端单独分配一个线程
来处理,否则无法实现“同时”。
public class Server1 {
public static void main(String[] args) throws IOException {
// 1、准备一个 ServerSocket
ServerSocket server = new ServerSocket(8888);
System.out.println("等待连接。。。。");
int count = 0;
while(true){
// 2、监听一个客户端的连接
Socket socket = server.accept();
System.out.println("第" + ++count + "个客户端"+socket.getInetAddress().getHostAddress()+"连接成功!!");
ClientHandlerThread ct = new ClientHandlerThread(socket);
ct.start();
}
}
//这里没有关闭 server,永远监听
static class ClientHandlerThread extends Thread{
private Socket socket;
private String ip;
public ClientHandlerThread(Socket socket){
super();
this.socket = socket;
ip = socket.getInetAddress().getHostAddress();
}
public void run(){
try{
//(1)获取输入流,用来接收该客户端发送给服务器的数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//(2)获取输出流,用来发送数据给该客户端
PrintStream ps = new PrintStream(socket.getOutputStream());
String str;
// (3)接收数据
while ((str = br.readLine()) != null){
//(4)反转
StringBuilder word = new StringBuilder(str);
word.reverse();
//(5)返回给客户端
ps.println(word);
}
System.out.println("客户端" + ip+"正常退出");
}catch (Exception e){
System.out.println("客户端" + ip+"意外退出");
}finally {
try {
//(6)断开连接
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class Client1 {
public static void main(String[] args) throws Exception{
// 1、准备 Socket,连接服务器,需要指定服务器的 IP 地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
// 2、获取输出流,用来发送数据给服务器
OutputStream out = socket.getOutputStream();
PrintStream ps = new PrintStream(out);
// 3、获取输入流,用来接收服务器发送给该客户端的数据
InputStream input = socket.getInputStream();
BufferedReader br;
if(args != null && args.length > 0){
String encoding = args[0];
br = new BufferedReader(new InputStreamReader(input, encoding));
}else {
br = new BufferedReader(new InputStreamReader(input));
}
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("输入发送给服务器的单词或成语:");
String message = scanner.nextLine();
if(message.equals("stop")){
socket.shutdownOutput();
break;
}
// 4、 发送数据
ps.println(message);
// 接收数据
String feedback = br.readLine();
System.out.println("从服务器收到的反馈是:" + feedback);
}
//5、关闭 socket,断开与服务器的连接
scanner.close();
socket.close();
}
}
5、UDP 网络编程
UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务,类似于短信。
通信模型
UDP 协议是一种
面向非连接
的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送,至于对方是否可以接收到这些数据内容,UDP 协议无法控制,因此说,UDP 协议是一种不可靠的
协议。
UDP 适用于一次只传送少量数据、对可靠性要求不高的应用环境,数据报大小限制在 64K 以下。
类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证 UDP 数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
DatagramPacket 对象封装了 UDP 数据报,在数据报中包含了发送端的 IP 地址和端口号以及接收端的 IP 地址和端口号。
UDP 协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。
开发步骤
发送端程序包含以下四个基本的步骤:
• 创建 DatagramSocket :默认使用系统随机分配端口号。
• 创建 DatagramPacket:将要发送的数据用字节数组表示,并指定要发送的数据长 度,接收方的 IP 地址和端口号。
• 调用 该 DatagramSocket 类对象的 send 方法 :发送数据报 DatagramPacket 对象。
• 关闭 DatagramSocket 对象:发送端程序结束,关闭通信套接字。
接收端程序包含以下四个基本的步骤 :
• 创建 DatagramSocket :指定监听的端口号。
• 创建 DatagramPacket:指定接收数据用的字节数组,起到临时数据缓冲区的效果, 并指定最大可以接收的数据长度。
• 调用 该 DatagramSocket 类对象的 receive 方法 :接收数据报 DatagramPacket 对象。
• 关闭 DatagramSocket :接收端程序结束,关闭通信套接字。
演示发送和接收消息
基于 UDP 协议的网络编程仍然需要在通信实例的两端各建立一个 Socket,但这两个 Socket 之间并没有虚拟链路,这两个 Socket 只是发送、接收数据报的对象,Java 提供了 DatagramSocket 对象作为基于 UDP 协议的 Socket,使用DatagramPacket 代表 DatagramSocket 发送、接收的数据报。
举例 1:
public class Receiver {
public static void main(String[] args) {
DatagramSocket ds = null;
try{
ds = new DatagramSocket(10000);
byte[] by = new byte[1024*64];
DatagramPacket dp = new DatagramPacket(by,by.length);
ds.receive(dp);
String str = new String(dp.getData(), 0, dp.getLength());
System.out.println(str + "--" + dp.getAddress());
}catch (Exception e){
e.printStackTrace();
}finally {
if(ds != null){
ds.close();
}
}
}
}
public class Sender {
public static void main(String[] args) {
DatagramSocket ds = null;
try{
ds = new DatagramSocket();
byte[] by = "hello,world".getBytes();
DatagramPacket dp = new DatagramPacket(by,0,by.length, InetAddress.getByName("127.0.0.1"),10000);
ds.send(dp);
}catch (Exception e){
e.printStackTrace();
}finally {
if(ds !=null){
ds.close();
}
}
}
}
举例2:
public class Receiver1 {
public static void main(String[] args) throws Exception{
// 1、建立接收端的 DatagramSocket,需要指定本端的监听端口号
DatagramSocket ds = new DatagramSocket(9999);
//一直监听数据
while (true){
//2、建立数据包 DatagramPacket
byte[] buffer = new byte[1024*64];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
//3、调用 Socket 的接收方法
ds.receive(dp);
//4、拆封数据
String str = new String(dp.getData(),0,dp.getLength());
System.out.println(str);
}
}
}
public class Sender1 {
public static void main(String[] args) throws Exception{
// 1、建立发送端的 DatagramSocket
DatagramSocket ds = new DatagramSocket();
//要发送的数据
ArrayList<String> all = new ArrayList<String>();
all.add("你好,强尼在这呢!");
all.add("here is johnny!");
all.add("啊啊啊啊啊");
//接收方的 IP 地址
InetAddress ip = InetAddress.getByName("127.0.0.1");
//接收方的监听端口号
int port = 9999;
//发送多个数据报
for(int i = 0; i < all.size(); i++){
//2、建立数据包 DatagramPacket
byte[] data = all.get(i).getBytes();
DatagramPacket dp = new DatagramPacket(data, 0,data.length,ip,port);
// 3、调用 Socket 的发送方法
ds.send(dp);
}
// 4、关闭 Socket
ds.close();
}
}
6、URL 编程
URL 类
• URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
• 通过 URL 我们可以访问 Internet 上的各种网络资源,比如最常见的 www,ftp 站点。浏览器通过解析给定的 URL 可以在网络上查找相应的文件或其他资源。
• URL 的基本结构由 5 部分组成:
<传输协议
>://<
主机名
>:<
端口号
>/<
文件名
>#
片段名
?
参数列表
• 例如:
http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
– 片段名:即锚点,例如看小说,直接定位到章节
– 参数列表格式:参数名=参数值&参数名=参数值....
• 为了表示 URL,java.net 中实现了类 URL。我们可以通过下面的构造器来初始化一个 URL 对象:
– public URL (String spec):通过一个表示 URL 地址的字符串可以构造一个 URL 对象。例如:
URL url = new URL("http://www. atguigu.com/");
– public URL(URL context, String spec):通过基 URL 和相对 URL 构造一个 URL 对象。例如:
URL downloadUrl = new URL(url, “download.html")
–public URL(String protocol, String host, String file); 例如:
URL url = new URL("http", "www.atguigu.com", “download. html");
– public URL(String protocol, String host, int port, String file); 例如:
URL
gamelan
=
new
URL
(
"http"
,
"www.atguigu.com"
,
80
,
“download
.
html
");
• URL 类的构造器都声明抛出非运行时异常,必须要对这一异常进行处理,通常是用 try-catch 语句进行捕获。
URL 类常用方法
一个 URL 对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获
取这些属性:
• public String getProtocol( ) 获取该 URL 的协议名
• public String getHost( ) 获取该 URL 的主机名
• public String getPort( ) 获取该 URL 的端口号
• public String getPath( ) 获取该 URL 的文件路径
• public String getFile( ) 获取该 URL 的文件名
• public String getQuery( ) 获取该 URL 的查询名
URL url = new URL("http://localhost:8080/examples/myTest.txt");
System.out.println("getProtocol() :"+url.getProtocol());
System.out.println("getHost() :"+url.getHost());
System.out.println("getPort() :"+url.getPort());
System.out.println("getPath() :"+url.getPath());
System.out.println("getFile() :"+url.getFile());
System.out.println("getQuery() :"+url.getQuery());
针对 HTTP 协议的 URLConnection 类
• URL 的方法 openStream():能从网络上读取数据
• 若希望输出数据,例如向服务器端的 CGI (公共网关接口-Common Gateway Interface-的简称,是用户浏览器和服务器端的应用程序进行连接的接口)程序发送一 些数据,则必须先与 URL 建立连接,然后才能对其进行读写,此时需要使用 URLConnection 。
• URLConnection:表示到 URL 所引用的远程对象的连接。当与一个 URL 建立连接时, 首先要在一个 URL 对象上通过方法 openConnection() 生成对应的 URLConnection 对象。如果连接过程失败,将产生 IOException.
– URL netchinaren = new URL ("http://www.atguigu.com/index.shtml");
– URLConnectonn u = netchinaren.openConnection( );
• 通过 URLConnection 对象获取的输入流和输出流,即可以与现有的 CGI 程序进行交互。
– public Object getContent( ) throws IOException
– public int getContentLength( )
– public String getContentType( )
– public long getDate( )
– public long getLastModified( )
– public InputStream getInputStream ( ) throws IOException
– public OutputSteram getOutputStream( )throws IOException