IO流:
io流中i表示input输入,o表示output输出,流表示数据(字符,字节,1个字符=2个字节=8个位);这里的输入输出是以内存为基础,将数据从内存中输出到硬盘的过程称为输出,将数据从硬盘中输入到内存中称为输入,这里的输入和输出就被称为io流;这个数据输入输出有字符和字节,所以又可分为字符流和字节流。
**字节流:**一切皆为字节,所有的文件都是以字节为单位存储的,一个字节=8个位,文件的读写是以字节为单位的。无论是什么流,最底层传输还是二进制数据;字节流又分为字节输出流OutputStream和字节输入流InputStream。
**OutputSteam输出流:**OutputSteam是所有输出流的顶级父类,此类为抽象类,其子类已知有: ByteArrayOutputStream往字节数组中写入数据的字节输出流、FileOutputStream往文件中写数据的字节输出流、FilterOutputStream带过滤器的字节输出流、ObjectOutputStream对象输出流、PipedOutputSream管道流
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
public class TestOutputStream {
// 1.OutputSteam是一个抽象类,此类定义了一些共性的方法和一些子类,如:FileOutputStream将内存中的数据写入到硬盘中的文件中:
// 2.FileOutputStream有两个构造方法,分别传入一个字符串或File,表示数据写入的目的地,实际就是一个路径。
// 3.FileOutputStream的作用:1.创建FileOutputStream对象 2.根据构造方法中的路径创建一个空文件 3.把FileOutputStream对象指向创建好的文件
// 4.内存写入数据到硬盘的原理:java程序-->jvm虚拟机-->os操作系统-->os调写数据的方法-->把数据写入到文件中
// 5.字节输出流的使用步骤:1.创建一个FileOutputStream对象,构造方法中传入写入数据文件的目的地(文件或路径)2.调用FileOutputStream对象中的write方法将数据写入到文件 3.释放系统资源(流在使用的过程中占用了一部分内存资源,使用完要释放掉,提高程序的效率,不释放也没关系)
public static void main(String[] args) throws IOException {
// 1.创建一个FileOutputStream对象
FileOutputStream fos = new FileOutputStream("D:\\00.java高级\\练习\\javaDemo\\075.OutputStream流\\hello.txt"); // 这个路径可以是相对路径,这里可能会抛出异常,需要处理
// 2.将数据写入到文件
fos.write(97); // write会抛出异常,需要处理,这里97表示小写的a,int类型表示的是字节对应的Ascall码值,97对应的字节写入到硬盘中最终是以二进制存储的,任意的文本编辑器在打开文件时都会去查Ascall码表或系统码表(gbk等),把字节转为字符表示。
fos.write('g'); // 直接写入一个字节,一个字节等于8个比特位(10101010),一个字节的比特位最大值11111111(十六进制FF、十进制255)
fos.write('中'); // 汉字在这里乱码了,但是还是可以写入的,
// 一次写入多个字节:1.支持byte数组 2.支持一部分byte数组写入(byte[] b, int off, int length),b表示byte数组,off表示开始位置,length表示写入数组的长度
// 如果写的第一个字节是正数(0-127)那么显示的时候会查Ascall表;如果写入的第一个字节是负数,那么第一个字节会和第二个字节组成一个中文,查询系统默认码表(gbk)
byte[] ls = {97,12,99,97,12,99};
fos.write(ls);
byte[] ls2 = {-97,-12,99};
fos.write(ls2);
byte[] lss = {97,12,99,97,12,99};
fos.write(lss,1,2);
// 写入字符串:利用字符串转byte数组api getByte
byte[] strls = "你好".getBytes();
System.out.println(Arrays.toString(strls)); // [-28, -67, -96, -27, -91, -67]
fos.write(strls);
// 文件中输出:ag-acac燈cc浣犲ソ,这里看到并没有你好,是因为这里是按utf-8字符编码集,三个字节为一个中文,而gbk中为两个字节为一个中文
// 3.释放资源:
fos.close();
// 4.追加数据:每次执行上面的代码实际都是将源文件覆盖掉的,如果想要在当前文件继续追加数据可以使用两个参数的构造方法FileOutputStream,其中第一个构造方法参数表示目标文件,第二个参数表示是否开启追加:
FileOutputStream fos2 = new FileOutputStream("D:\\00.java高级\\练习\\javaDemo\\075.OutputStream流\\append.txt", true);
fos2.write('g');// 每次执行都会追加字符上去
fos2.write("\r\n".getBytes()); // 换行:win(\r\n)、linux(/n)、mac(/r)
fos2.write("你好".getBytes());
fos2.close();
};
}
InputStream字节输入流: 此类也是io流类的一个子类,它也是抽象的,并且是所有字节输入流的父类,它下面还有一些子类:BufferedInputStream、AudioInputStream读取音频、ByteArrayInputStream读取字节数组、FileInputStream读取文件、DataInputStream、FilterInputStream带过滤器的、read()、OutputStream、PushbackInputStream、ObjectInputStream读取对象的、PipedInputStream管道流、SequenceInputStream处理队列的、StringBufferInputStream读取字符串缓存区
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
public class TestFileInputStream {
public static void main(String[] args) throws IOException {
// 1.FileInputStream文件字节流输入流,将硬盘中文件中的数据读取到内存中,其构造方法中参数表示要读取文件的路径或文件
// 2.构造方法的作用:1.创建一个FileInputStream对象 2.将FileInputStream对象指定构造方法中要读取的文件
// 3.读取数据原理:java程序-->jvm-->os-->os读取数据的方法-->读取文件
// 4.字节输入流的使用步骤:1.创建一个FileInputStream对象,构造方法参数指明文件 2.调用FileInputStream的read方法读取文件 3.调用FileInputStream的close方法释放资源
// 1.创建FileInputStream对象:
FileInputStream fis = new FileInputStream("D:\\00.java高级\\练习\\javaDemo\\076.InputStream流\\test.txt");
int num;
do {
num = fis.read(); // 2.读取文件中的一个字节并返回,读取到文件末尾返回-1
System.out.println(num);
System.out.println((char)num);
byte[] ls = new byte[2];
int num2 = fis.read(ls); // read传入一个字节数组时读取字节数组长度的字节并返回给字节数组
System.out.println(Arrays.toString(ls)); // [0, 0]
System.out.println(num2);
System.out.println((char)num2);
System.out.println("---------------");
} while (num != -1);
// 3.释放资源:
fis.close();
};
}
文件复制demo:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 创建读文件的流:将内存中的数据流输出(读取),写入到文件中
FileOutputStream fos = new FileOutputStream("D:\\00.java高级\\练习\\javaDemo\\076.InputStream流\\copy_test.mp4");
// 创建写文件的流:将文件读取到内存缓存区
FileInputStream fis = new FileInputStream("D:\\00.java高级\\练习\\javaDemo\\076.InputStream流\\test.mp4");
// 一读一写实现文件拷贝:
int bts = 0; // 记录每次读取有效字节个数
// 循环将硬盘中的文件数据写入内存,如果不指定字节数量,每次都会读取一个字节写入一个字节,这样速度很慢,可以读取多个字节写入多个字节
byte[] size = new byte[1024 * 50]; // 值越大,速度越快
// while((bts = fis.read()) != -1) {
while((bts = fis.read(size)) != -1) { // 优化读取多个字节
// 将内存中的流输出到新的文件中:
// fos.write(bts);
fos.write(size,0,bts); // 优化写入多个字节
};
//释放资源:
fos.close();
fis.close();
};
}
**字符流:**字节流读取中文时可能会有显示不全乱码的问题,因为中文占有多个字节存储,为解决这个问题,java提供了字符流类,以字符为单位读写数据,专门用于处理文本文件的Reader类,此类为字符输入流最顶层的类,此类也是一个抽象类,其子类有:BufferedReader缓存流、CharArrayReader字符数组流、FilterReader过滤流、InputStreamReader转换流、PipedReader管道流、StringReader读取字符串流
一个中文如果是GBK编码则占用2个字节,如果是utf-8编码则占用3个字节。
**字符输入流:**将硬盘文件中的数据以字符的方式读取到内存中
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class TestReader {
// 1.Reader字符输入流:是字符输入流的最顶层父类,定义了一些共性的成员方法,是一个抽象类
// 2.共性方法:read()读取单个字符并返回、read(char[] cbuf)一次读取多个字符,将字符读取到数组
// 3.close()关闭该流并释放与之关联的所有资源
// 4.fileReader继承于InputStreamReader,InputStreamReader又继承于Reader,fileReader文件字符输入流,把硬盘文件中数据以字符的方式读取到内存中
// 5.构造参数:FileReader()里面可以传入一个文件路径或一个文件实例
// 6.fileReader构造方法的作用:1.创建一个FileReader对象 2.把FileReader对象指向要读取的文件
// 7.字符输入流的使用步骤:1.创建FileReader对象,构造方法中绑定要读取的数据源 2.使用FileReader对象中的read方法读取文件 3.释放资源
public static void main(String[] args) throws IOException {
// 1.创建FileReader对象:
FileReader fs = new FileReader("D:\\00.java高级\\练习\\javaDemo\\077.Reader字符流\\test.txt");
// 2.使用reader读取字符:
// 读取单个字符
//int ln = 0; // 读取字符的长度
//while((ln = fs.read()) != -1){
// System.out.println(ln);
//};
// 读取多个字符:
int ln2 = 0;
char[] ls = new char[1024 * 50];
while((ln2 = fs.read(ls)) != -1){
System.out.println(new String(ls,0,ln2));
};
// 3.释放资源:
fs.close();
};
}
字符输出流: writer类是io类的一个子类,是所有字符输出流的最顶层父类,是一个抽象类,其子类有:BufferedWriter、CharArrayWriter、FilterWriter、OutputStreamWriter、PipedWriter、PrintWriter、StringWriter
import java.io.FileWriter;
import java.io.IOException;
public class TestFileWriter {
// 1.FileWriter: 把内存中的字符数据写入到文件中
// 2.构造方法FileWriter其中可以传递一个文件路径或者文件实例,其作用:1.创建一个FileWriter对象 2.根据构造方法中的文件或文件路径创建文件 3.把FileWriter对象指向创建好的文件
// 3.使用步骤:1.创建FileWriter对象,构造方法中绑定要写入数据的目的地 2.使用write方法将数据写入到内冲缓存区中(字符转字节的过程)3.使用flush将内存缓存区中的数据刷新到文件中 4.释放资源(会把内存缓存区中的数据刷新到文件中)
public static void main(String[] args) throws IOException {
// 1.创建FileWriter对象:
FileWriter fw = new FileWriter("D:\\00.java高级\\练习\\javaDemo\\078.Writer字符输出流\\hello.txt", false); // 第二个参数表示是否开启续写,开启后每次执行文件都会进行追加写入,否则会创建新文件覆盖原文件
// 2.使用FileWriter中的write把数据写入到内存缓冲区(字符转换为字节流的过程)
fw.write(97); // 此时并没有将数据直接写入到文件中,而是写入内存缓冲区
// write可直接传递一个字符数组进行写入:
char[] list = {'a','b','c'};
fw.write(list);
// write可直接写入字符串或字符串一部分:
fw.write("hello123");
fw.write("今天又是摸鱼的一天", 2, 3);
// 换行写:win:\r\n linux:/n mac:/r
fw.write("\r\n");
// write可以指定字符数组某一个部分写入:
fw.write(list,1,2);
// 3.使用FileWriter中的flush方法,把内存缓冲区中的数据刷新到文件中
fw.flush(); // 如果调用了close方法,会自动刷新数据到文件中
fw.close();
};
// 4.flush方法和close方法的区别:flush刷新缓冲区,流对象可以继续使用;close先刷新缓冲区,然后通知系统释放资源,流对象不可以再使用了
}
属性集Properties:
可以创建一个键值对的集合,并搭配流可以将数据存储到文件或从文件中将数据读取到集合中。
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
public class PropertiesTest {
// 1.Properties继承于Hashtable,该类表示了一个持久的属性集,该属性可保存在流中或从流中加载,Properties是一个唯一和io流相结合的集合
// 2.可以使用Properties集合中的方法store,把集合中的临时数据持久化的写入到硬盘中,可以使用load方法将文件中(键值对),读取到集合中
// 3.属性列表中每个键及其对应值都是一个字符串,Properties集合是一个双链集合,key和value默认都是字符串
// 4.Properties集合有两个常用方法:setProperty(String key, String value)类似调用Hashtable的put进行存储值、通过getProperty(String key)通过key找到value,类似Map中的get方法;还有一个遍历属性key的方法stringPropertyNames,它返回此属性列表中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法
public static void main(String[] args) throws IOException {
// 1.创建一个Properties集合:
Properties ps = new Properties();
// 2.往集合中添加属性及值:
ps.setProperty("name", "小迪");
ps.setProperty("age", "18");
// ps.put(12,12); // 支持put方法,但是不推荐
// 3.遍历集合:
Set<String> set = ps.stringPropertyNames();
for (String key: set) {
// 拿到key
System.out.println(key);
// 拿到值:
System.out.println(ps.getProperty(key));
};
// 4.store方法将数据写入到硬盘中,传递的如果是字节流,那么就不能写入中文,如果传递的是字符流,才可以写中文进去,store还可以传入注释(注释不能使用中文,不然会乱码),用来说明保存文件是做什么用的
// 创建字节或字符输出流对象:
FileWriter fw = new FileWriter("D:\\00.java高级\\练习\\javaDemo\\079.PropertiesTest\\test.txt");
// 将Properties集合中的数据存储到文件中:
ps.store(fw,"save data test"); // store可以把数据持久化到硬盘中,第一个参数为流对象,参数二为说明; 数据以key=value的形式保存在了文件中
// 释放资源:
fw.close();
// 6.load方法可以将文件中的键值对读取到集合中:参数如果传递了字节输入流,那么就不能读取中文键值对;如果传递了字符输入流,就可以读取中文:
// 使用步骤:1.创建Properties集合对象 2.调用集合对象的方法load将文件中的值读取到集合中 3.遍历集合对象查看值
// 注意事项:存储数据的文件中键与值之间可以使用=或其它符合连接、存储键值对的文件中可以#开头为注释,注释不会被读取到集合中、键和值默认就是字符串形式,无需再加引号:
Properties ps2 = new Properties();
ps2.load(new FileReader("D:\\00.java高级\\练习\\javaDemo\\079.PropertiesTest\\test.txt"));
Set<String> lskey = ps2.stringPropertyNames();
for (String key: lskey) {
System.out.println(key + "---" + ps.getProperty(key));
};
};
}
缓冲流:
缓冲流能够转换编码,能持久化存储对象的序列化流等,缓冲流都是在基础的流上面创建而来的,是将多个基本流存到数组中之后一次进行处理。 缓冲流也叫高效流,是对4个基本的FileXXX流的增强,它们分别为:
字节缓冲流:BufferedInputStream、BufferedOutputStream
字符缓冲流: BufferedReader、BufferedWriter
前面的基本流在读写的时候都是非常慢的,缓冲流相对于基本流快很多。
字节缓冲输出流和字节缓冲输入流:
import java.io.*;
public class BufferedOutInputStreamTest {
// 字节缓冲输出流部分:
// 1.BufferedOutputStream基础于OutputStream,构造方法:BufferedOutputStream(),里面可以传递OutputStream来创建一个缓冲流输出流,以将数据写入到指定的底层输出流;另一种方式是在第一个基础上面在传递第二个参数指定缓冲区大小
// 2.使用步骤:1.创建FileOutputStream对象,构造方法中指定要输出的目的地 2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率 3.使用BufferedOutputStream对象中的write将数据写入到指定的缓冲区中 4.使用flush把内部缓冲区中的数据刷新到文件中 5.close释放资源
public static void main(String[] args) throws IOException {
// 1.创建基本的输出流,构造方法中指定要输出的目的地:
FileOutputStream fi = new FileOutputStream("D:\\00.java高级\\练习\\javaDemo\\080.缓冲流\\test.txt");
// 2.创建一个缓冲流对象:里面传递基本输出流对象
BufferedOutputStream bos = new BufferedOutputStream(fi);
// 3.调用缓冲流对象的write写数据:write中传递字节数组
bos.write("一些数据被写入到内部缓冲区中:".getBytes()); // 此时只是创建了文件,但是数据是还没有刷新到文件里面
// 4.刷新数据到文件中:
bos.flush();
// 5.释放资源:
bos.close();
// 字节缓冲输入流部分:
// 1.BufferedInputStream继承于InputStream,构造方法可以传递一个参数输入流,也可以指定第二个参数指定缓冲区大小
// 2.使用步骤哦:1.使用FileInputStream创建一个基本字节输入流,构造方法中绑定要读取的数据源 2.使用BufferedInputStream创建对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
// 3.使用BufferedInputStream对象中的read方法读取文件
// 4.释放资源
// 1.创建基本输入流:绑定要输入的文件源:
FileInputStream fis = new FileInputStream("D:\\00.java高级\\练习\\javaDemo\\080.缓冲流\\test.txt");
// 2.创建BufferedInputStream对象,构造方法中传递基本流对象:
BufferedInputStream bis = new BufferedInputStream(fis);
// 3.使用read方法读取文件:
int ln = 0;
//while((ln = bis.read()) != -1){
// System.out.println(ln);
//};
// 优化为每次读取一部分字节:
byte[] list = new byte[1024 * 50];
while((ln = bis.read(list)) != -1){
System.out.println(new String(list,0,ln));
};
// 释放资源:
bis.close();
};
}
字符缓冲输出流和字符缓冲输入流:
import java.io.*;
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
// 字符缓冲输出流部分:
// 1.BufferedWriter继承于Writer类,它为字符缓冲流
// 2.构造方法:BufferedWriter里面可以传递一个默认大小的输出缓冲字符输出流,也可以接收第二个参数来指定大小输出缓冲区,提高FileWriter的写入效率
// 3.BufferedWriter特有方法:newLine(),写入一个分隔符,会根据不同的操作系统获取不同的航分割符
// 4.使用步骤:
// 1.创建字符缓冲输出流对象,构造方法中传递字符输出流
// 2.调用字符缓冲输出流的方法write,把数据写入到内存缓冲区中
// 3.调用字符缓冲输出流中的flush方法将数据刷新到文件中
// 4.释放资源
// 1.创建BufferedWriter对象,里面接收一个FileWriter实例
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\00.java高级\\练习\\javaDemo\\080.缓冲流\\test2.txt", true));
// 2.往文件中写入内容:循环写入数据,并测试newLine方法进行换行:
for (int i = 0; i < 5; i++){
bw.write("写入字符串:"+i);
bw.newLine();
};
// 3.刷新数据到文件中:
bw.flush();
// 4.释放资源:
bw.close();
// 字符缓冲输入流部分:
// 1.BufferedReader继承于Reader方法
// 2.构造方法:BufferedReader用于创建一个使用默认大小输入缓冲区的缓冲字符输入流,可以接收第二个参数来指定缓冲区的大小
// 3.使用步骤: 1.创建字符缓冲输入流对象,构造方法中传递字符输入流 2.使用字符缓冲区输入流对象的read或readLine读取文本 3.释放资源
// 1.创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("D:\\00.java高级\\练习\\javaDemo\\080.缓冲流\\test3.txt"));
// 2.读取文本:
//int st = br.read(); // read只能读取单个字符的值,使用readLine可以读取每行字符串:
//System.out.println(st);
String sl = br.readLine(); // readLine每次只能读取一行内容,如果没有内容过来就会返回null
System.out.println(sl);
String newStr;
while((newStr = br.readLine()) != null){
System.out.println(newStr);
};
// 3.释放资源:
br.close();
};
}
转换流:
字符编码:
按照某种规则,将字符储存到计算机中称为编码,反之将计算机中的二进制数按照某种规则解析显示出来称为解码;字符编码就是一套自然语言的字符与二进制之间的对应规则;编码表:生活中文字和计算机二进制的对应规则
字符集: 字符集也叫编码表,是一个系统支持所有字符的集合,包括各国加文字、标点符号、图形符号、数字等;常见的字符集有:Ascall字符集、GBK字符集、Unicode字符集。
当一个文件使用的编码和读取文件使用的编码不一致时,此时读取到就会发生乱码,为解决乱码问题,可以使用转换流。
InputStreamReader和OutputStreamWriter是转换流的桥梁:
OutputStreamWriter是字符流通向字节流的桥梁,可以指定字符编码将要写入的字符编码成字节,就是将能看懂的转换为看不懂的,是编码的过程。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class OutputStreamWriterDemo {
// 1.OutputStreamWriter的构造方法第一个参数是一个文件输出流,也可以传递第二个参数用来指定字符编码,将字符编码后以字节的形式存储在文件
// 2.使用步骤:1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定编码表名称 2.调用write方法将字符转换为字节存储到缓冲区中(进行编码)3.调用flush方法,把缓冲区中的字节刷新到文件中 4.释放资源
public static void main(String[] args) throws IOException {
// 1.创建一个OutputStreamWriter对象:
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\00.java高级\\练习\\javaDemo\\081.OutputStreamWriter\\test.txt"),"gbk"); // 第二个参数指定编码格式后将以此编码格式对文件编码存储
// 2.写数据:
osw.write("你好123");
// 3.刷新文件:
osw.flush();
// 4.释放资源:
osw.close();
};
}
InputStreamReader:将字符文件读取出来以,可以指定编码格式进行解码,不懂的字符到可以看懂的字节的过程。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
// 1.InputStreamReader传递第一个参数为字节输入流,也可以传递第二个参数指定编码集
// 2.使用步骤:1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码名称(这里编码格式要和文件的一致,否则会乱码) 2.使用InputStreamReader的read方法读取文件 3.释放资源
public static void main(String[] args) throws IOException {
// 1.创建InputStreamReader对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\00.java高级\\练习\\javaDemo\\081.OutputStreamWriter\\test.txt"), "gbk"); // 文件是gbk,读取的时候也要用gbk,否则会乱码
// 2.读取文件:
int ln = 0;
while((ln = isr.read()) != -1){
System.out.println((char)ln);
};
// 3.释放资源
isr.close();
};
}
序列化和反序列化:
对象序列化流:将对象以字节流的方式存储
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjectOutputStreamTest {
// 对象序列化:
// 1.ObjectOutputStream继承于OutputStream,其作用是将对象以流的方式写入到文件中保存
// 2.构造方法:构造方法接收一个字节输出流
// 3.特有方法:writeObject(Object obj),将对象写入到ObjectOutputStream,需要特别注意:这里传入的对象需要实现序列化和反序列化接口Serializable,只需要实现做标记,其它不用做,否则会抛异常
// 4.使用步骤:1.创建ObjectOutputStream对象,构造方法中传递字节输出流 2.使用其方法writeObject方法把对象写入到文件中 3.释放资源
public static void main(String[] args) throws IOException {
// 1.创建ObjectOutputStream对象,构造方法中传递输出流:
ObjectOutputStream ojos = new ObjectOutputStream(new FileOutputStream("D:\\00.java高级\\练习\\javaDemo\\082.对象序列化\\test.txt"));
// 2.使用writeObject写入一个对象:
ojos.writeObject(new Person("韩梅梅", 25)); // 这里是以字节的方式存储的,直接打开查看是乱码的
// 3.释放资源:
ojos.close();
};
}
对象的反序列化流:把文件中储存的对象以流的方式读取出来。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputStreamTest {
// 1.ObjectInputStream继承于InputStream,其作用是对象的反序列化,把文件中储存的对象以流的方式读取出来
// 2.构造方法:接收一个指定的InputStream字节输入流:
// 3.使用步骤:1.创建objectInputStream对象,构造方法中传递字节输入流 2.使用其特有方法readObject读取对象文件 3.释放资源 4.使用读取出来的对象打印
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1.创建ObjectInputStream对象,里面传递文件输入流
ObjectInputStream ojis = new ObjectInputStream(new FileInputStream("D:\\00.java高级\\练习\\javaDemo\\082.对象序列化\\test.txt"));
// 2.使用其特有方法readObject读取文件流
Object ob = ojis.readObject();
// 3.释放资源:
ojis.close();
// 4.查看读取到的对象:
System.out.println(ob);
// ob是Object类型,将转Person类型:
Person p = (Person)ob;
System.out.println(p.getNames()); // 韩梅梅
};
}
提示:静态属性是不能被序列化的,即使序列化了,它的值还是默认的值,在创建对象时传递的值并不会赋值成功,除了static修饰的静态属性外,还有一个关键字:transient关键字修饰的成员变量也是不能被序列化的,此时如果不想成员变量被序列化,可以使用此关键字,但它却没有静态的含义。
在序列化和反序列化对象时,在对象文件中加入:private static final long serialVersionUID = 一个值;可以有效防止序列化冲突报错的问题
打印流
PrintStream为打印流,其特点:只负责数据的输出,不负责数据的读取,永远不会抛出IOException
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class PrintStreamDemo {
// 1.PrintStream为打印流,继承于OutputStream,为其它输出流添加了功能,使它们能够方便地打印各种数据值表示形式
// 2.特有方法print和println
// 3.构造方法:参数可以是指定输出的目的地是一个文件或字节输出流或一个文件路径
public static void main(String[] args) throws FileNotFoundException {
// 1.创建一个打印流对象:
PrintStream ps = new PrintStream("D:\\00.java高级\\练习\\javaDemo\\083.printStream打印流\\error.txt");
// 2.写入打印信息:
ps.write('a');
ps.write(98);
ps.println("你好哈"); // println或print打印任意数据类型,且不会自动转换为字节,如打印97并不会显示a;而write是会自动转换为字节的,而且不支持字符串打印
// 3.System.setOut(打印流对象);可以将以下print的东西打印到输出文件中,特别提醒:setOut方法前面的不会不打印到文件中,而是继续在控制台打印
System.setOut(ps);
System.out.println("123sdfs");
// 4.释放资源:
ps.close();
};
}
网络编程:
软件结构:
c/s结构:全称client/server结构,指客户端和服务器结构,常见程序有:qq、网盘等。
b/s结构:全称browser/server结构,指浏览器和服务器结构,常见浏览器有谷歌、火狐等都是通过网址去访问。
两种架构都需要网络,网络编程就是在一定的协议下实现两台计算机的通信的程序。
网络通信协议: 通过计算机网络可以实现多台计算机的连接,位于同一网络中的计算机在进行连接和通信的时候需要遵循一定的规则,这些规则被称为网络协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵循协议才能完成数据交换。
TCP/ip协议: 也叫传输控制协议/英特网互联协议Transmission Control Protocol/Internet Protocol,是因特网最基础应用最广泛的协议,它内部包含一系列用于处理数据通信的协议,采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
协议分类: 通信的协议还是比较复杂的,java.net包中包含的类和接口它们提供了低层次的通信细节,我们可以直接使用这些类和接口来专注于网络程序的开发,而不用考虑通信的细节。java.net包中提供了两种常见的网络协议的支持:
UDP: 用户数据报协议User Datagram Protocol,UDP是无连接通信协议,数据的发送端和接收端无需建立逻辑连接,简单的说:当一台计算机向另一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在接收数据时,也不会向发送端反馈是否收到数据,其特点:耗资小、通信效率高,常用语音频和视频及普通数据的传输,例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个包也不会对接收结果产生太大影响,弊端:一次发送的数据不能超过64kb,超出这个范围就不能发送了。
TCP:传输控制协议Transmission Control Protocol,是面向连接的通信协议,即传数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,其特点:两台计算机之间可无差错的进行数据传输,保证数据传输的安全,应用十分广泛,如文件下载,浏览网页等。客户端和服务端每次连接都是由客户端发出连接请求,每次连接的创建都需要3次握手,3次握手完后才进行数据交换:
网络编程三要素:
协议:计算机网络通信必须遵循网络规则
ip地址:指互联网协议地址lnternet Protocol Address,俗称ip,计算机设备的唯一编号;ip可分IPv4和IPv6, IPv4是一个32位的二进制数通常被分为4个字节,表示成a.b.c.d, Ipv6是为了扩大地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成:ABCD: EFe1: 2345: 6789: ABCD: EF01: 2345: 6789,这样就解决了网络地址资源数量不够用的问题。
查看本地网络配置命令:终端输入:ipconfig
端口:端口是计算机对每一个程序的编号,端口号有2个字节组成,取值范围在0~65535,这个编号并非和某个程序绑定死,而是可以指定的。如后端启动某个服务端,指定端口号是:3000,那么前端通过ip加3000这个端口就可以找到这个服务。
TCP通信程序:
TCP通信能实现两台计算机之间数据交互,通信的两端,要严格区分为客户端与服务端。
两端通信时步骤:1.服务端程序,需要先启动,等待客户端的连接。2.客户端主动连接服务器端,连接成功才能通信,服务器不可以主动连接客户端。
在java中提供了两个类用于实现TCP通信程序:
1.客户端:java.net.Socket类用户创建客户端Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
2.服务端:java.net.ServerSocket类用于创建服务端对象,相当于开启一个服务,等待客户端的连接。
服务器端代码:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
// TCP通信服务端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据需要使用ServerSocket类,此类实现服务器套接字
// 1.构造方法:ServerSocket(int port),此类创建特定端口的服务器套接字
// 2.服务器必需明确是哪个客户端请求的服务器,所以可以使用accept方法获取到请求的客户端对象Socket
// 3.使用步骤:1.创建服务器ServerSocket对象和系统要指定的端口号 2.使用ServerSocket对象的accept方法获取客户端Socket对象 3.使用Socket对象中的方法getInputStream获取InputStream对象 4.使用网络字节输入流InputStream对象的read方法,读取客户端发送的数据 5.使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象 6.使用网络字节输出流OutputStream对象中的write方法给客户端写数据 7.释放资源(Socket,ServerSocket)
public static void main(String[] args) throws IOException {
// 1.创建服务器端ServerSocket对象,并指定端口:
ServerSocket servers = new ServerSocket(3000);
// 2.获取请求服务器端的客户端Socket对象:
Socket client = servers.accept();
// 3.使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象:
InputStream iso = client.getInputStream();
// 4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据:
byte[] bts = new byte[1024];
int ln = iso.read(bts);
String msg = new String(bts,0,ln);
System.out.println("服务器接收到数据:" + msg);
// 5.使用Socket对象中的getOutputStream方法获取网络字节输出流OutputStream对象
OutputStream ois = client.getOutputStream();
// 6.服务器回复客户端:
ois.write("你好,客户端,我是服务器A".getBytes());
// 7.释放流:
client.close();
servers.close();
};
}
客户端代码:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
// TCP客户端实现:客户端实现使用Socket类,此类实现客户端套接字(套接字是两台机器间通信的端点,套接字包含了ip和端口的网络单位)
// 1.构造方法:Socket(String host, int port),参数一 为服务器的ip或主机名,参数二为端口号
// 2.成员方法:1.OutputStream getOutputStream()返回此套接字的输出流 2.InputStream getInputStream()返回此套接字的输入流 3.close()关闭此套接字
// 3.实现步骤:1.创建一个客户端实例对象Socket,构造方法中传递ip和端口 2.使用Socket对象中的getOutputStream()方法获取网络字节输出流OutputStream对象 3.使用网络字节输出流OutputStream对象中的write方法给服务器发送数据 4.使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象 5.使用网络字节输入流InputStream对象中的read方法读取服务器回显的数据 6.释放资源(Socket)
// 4.提示:1.客户端和服务端进行交互时,必须使用Socket提供的网络流,不能使用自己创建的流对象 2.当我们创建客户端Socket对象时,此时就会去请求服务端和服务器经过3次握手建立连接,这时服务器如果没有启动就会抛出异常,如果服务器启动,那么就可以正常进行交互。
public static void main(String[] args) throws IOException {
// 1.创建Socket客户端对象:里面传递ip和端口号:
Socket client = new Socket("127.0.0.1", 3000);
// 2.使用Socket实例对象的getOutputStream得到一个字节输出流:
OutputStream ots = client.getOutputStream();
// 3.调用输出流的write方法向服务器写数据:
ots.write("你好服务器,我是客户端001".getBytes());
// 4.使用Socket对象中的getInputStream方法获取网络字节输入流InputStream对象:
InputStream iso = client.getInputStream();
// 5.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据:
byte[] bts = new byte[1024];
int ln = iso.read(bts);
String msg = new String(bts,0,ln);
System.out.println("客户端接收到数据:" + msg);
// 6.释放资源:
client.close();
};
}
文件上传案例(图片):
服务端代码:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class TcpServer {
// 服务端实现步骤:
public static void main(String[] args) throws IOException {
// 1.创建服务器ServerSocket对象,指定端口和ip:
ServerSocket server = new ServerSocket(3000);
// 优化:循环一直监听是否有客户端访问,有访问就执行,起监听作用:
while(true){
// 2.使用ServerSocket中的accept方法获取客户端Socket对象:
Socket client = server.accept();
// 优化:当有多个客户端上传文件时,使用多线程:
new Thread(new Runnable(){
@Override
public void run() {
try {
// 3.使用Socket中的getInputStream获取网络字节输入流InputStream对象
InputStream inStream = client.getInputStream();
// 4.判断服务器中是否有需要存储文件的文件夹,如果没有需要创建文件夹:
File files = new File("D:\\00.java高级\\练习\\javaDemo\\085.tcp实现文件上传\\uploads");
if (!files.exists()) {
files.mkdir();
}
// 5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要存储文件的目的地:
String strTemp = "\\files" + System.currentTimeMillis() + new Random(999999999).nextInt() + ".png";
FileOutputStream outStream = new FileOutputStream(files + strTemp);
// 6.使用网络字节输入流InputStream对象中的read读取客户端上传的文件:
int len = 0;
byte[] bytes = new byte[1024 * 1];
while((len = inStream.read(bytes)) != -1){
// 7.使用本地字节输出流FileOutputStream对象中的write将文件写入到服务器硬盘中:
outStream.write(bytes,0,len);
};
// 8.使用Socket对象中的getOutputStream获取到网络字节输出流OutputStream对象
OutputStream outStreamToClient = client.getOutputStream();
// 9.使用Socket获取的OutputStream对象回写数据给客户端:
outStreamToClient.write("文件上传成功!".getBytes());
// 10.释放资源
outStream.close();
client.close();
} catch (IOException e) {
System.out.println("异常:" + e);
}
}
}).start();
}
// 服务器一直处于监听客户端进来,这里也没必要关闭:
// server.close();
};
}
客户端代码:
import java.io.*;
import java.net.Socket;
public class TcpClient {
// 客户端实现步骤:
// 1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源:
// 2.创建一个客户端Socket对象,构造方法中绑定服务器ip地址和端口号
// 3.使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
// 4.使用本地字节输入流FileInputStream对象中的read,读取本地文件
// 5.使用网络字节输出流OutputStream对象中的write,把读取到的文件上传到服务器
// 6.使用Socket中的方法getInputStream获取网络字节输入流InputStream对象
// 7.使用网络字节输入流中InputStream对象的read读取服务器回写的数据
// 8.释放资源(FileInputStream,Socket)
public static void main(String[] args) throws IOException {
// 1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源:
FileInputStream pickFile = new FileInputStream("D:\\00.java高级\\练习\\javaDemo\\085.tcp实现文件上传\\test.png");
// 2.创建一个客户端Socket对象,构造方法中绑定服务器ip地址和端口号
Socket client = new Socket("127.0.0.1",3000);
// 3.使用Socket中的方法getOutputStream获取网络字节输出流OutputStream对象
OutputStream outStream = client.getOutputStream();
// 4.使用本地字节输入流FileInputStream对象中的read,读取本地文件
int len = 0;
byte[] bytes = new byte[1024 * 1];
while((len = pickFile.read(bytes)) != -1){
// 5.使用网络字节输出流OutputStream对象中的write,把读取到的文件上传到服务器
outStream.write(bytes, 0, len);
};
// 为防止服务器和客户端相互等待进入卡死状态,这里文件上传完后客户端终止继续写入数据
client.shutdownOutput();
// 6.使用Socket中的方法getInputStream获取网络字节输入流InputStream对象
InputStream inStream = client.getInputStream();
while((len = inStream.read(bytes)) != -1){
// 7.使用网络字节输入流中InputStream对象的read读取服务器回写的数据,然后打印:
System.out.println(new String(bytes,0,len));
};
// 8.释放资源(FileInputStream,Socket)
pickFile.close();
client.close();
};
}
B/S服务案例:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class WebServer {
public static void main(String[] args) throws IOException {
// 1.创建一个SocketServer对象,并指定一个端口:
ServerSocket server = new ServerSocket(3000);
while(true){
new Thread(new Runnable(){
@Override
public void run(){
// 2.使用SocketServer对象的accept方法获取客户端请求对象:
try {
Socket client = server.accept();
// 3.使用Socket中的getInputStream方法获取Socket对象中的InputStream对象:
InputStream inStream = client.getInputStream();
// 3.使用InputStream对象中的read方法读取客户端请求信息:
//byte[] bytes = new byte[1024];
//int len = 0;
//while((len = inStream.read(bytes)) != -1){
// System.out.println(new String(bytes,0,len));
// // 浏览器输入localhost:3000后这里打印信息:
// // GET / HTTP/1.1 // 注意: 这里实际是可以拿到访问路径的,如果在浏览器输入:http://localhost:3000/webhtml/index.html,可以看到打印信息:GET /webhtml/index.html HTTP/1.1, 不难看出GET后第一个斜杠开始到空格即是访问路径
// // Host: localhost:3000
// // Connection: keep-alive
// // sec-ch-ua: "Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"
// // sec-ch-ua-mobile: ?0
// // sec-ch-ua-platform: "Windows"
// // Upgrade-Insecure-Requests: 1
// // User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36
// // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
// // Sec-Fetch-Site: none
// // Sec-Fetch-Mode: navigate
// // Sec-Fetch-User: ?1
// // Sec-Fetch-Dest: document
// // Accept-Encoding: gzip, deflate, br
// // Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
//};
// 优化改造:从请求打印可以看出,我们只有第一行内容暂时对于我们有效,我们可以只读第一行内容,因此上面部分注释,看下面:
// 将InputStream字节输入流转换为字符缓冲输入流:
BufferedReader binStream = new BufferedReader(new InputStreamReader(inStream));
// 使用readLine读取一行:
String url = binStream.readLine();
System.out.println(url); // GET /webhtml/index.html HTTP/1.1 ,可以看到第一行请求路径已经拿到了,此时我们继续处理拿到对我们有用的部分:/webhtml/index.html
String[] urlList = url.split(" "); // 使用split以空格进行切割,得到一个字符串数组,拿到索引为1的字符串:
System.out.println(urlList[1]); // /webhtml/index.html , 去掉字符串前面的第一个斜杠:
String webUrl = urlList[1].substring(1); // 使用substring从1位置开始截取字符串:
System.out.println(webUrl); // webhtml/index.html
// 4.创建本地字节输入流,构造方法中绑定html文件路径:
FileInputStream finStrem = new FileInputStream(webUrl);
// 5.使用Socket对象中的getOutputStream方法获取OutputStream网络字节输出流:
OutputStream outStream = client.getOutputStream();
// 6.使用OutputStream中的write方法写数据给客户端:
// 写入固定给浏览器识别的信息:
outStream.write("HTTP/1.1 200 0K\r\n".getBytes());
outStream.write("Content-Type:text/html\r\n".getBytes());
outStream.write("\r\n".getBytes());
// 读取html文件并回写给客户端,因为这里文件可能太大,一般都是循环读完为止:
byte[] bytes = new byte[1024];
int len = 0;
while((len = finStrem.read(bytes)) != -1){
outStream.write(bytes,0,len);
};
// 7.释放资源:
finStrem.close();
client.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
}).start();
// server.close(); // 一直监听,所以不用关闭
}
// 提示: 这里考虑到多个客户访问及web项目中自发请求的问题,这里做了循环监听及多线程处理:只需要将Socke获取客服端部分及读取文件以及回写放到多线程中,并以循环的方式不断监听即可
};
}
提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:810665436@qq.com联系笔者删除。
笔者:苦海