A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 卞建彬 中级黑马   /  2018-9-28 16:55  /  563 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文


day10 缓冲流 转换流 序列化流 打印流




基本流: FileXxx
包装流:
    包装流只是在基本流的外面增加了一些便捷的功能, 最终进行读写的还是基本流
    但是包装流增强了基本流的功能
   


缓冲流


缓冲流的原理


缓冲流的原理:
    底层也是使用基本流(FileXxx)来读写
    但缓冲流内部定义了一个缓冲数组, 在读的时候类似于我们一次读一个数组的方式, 减少了磁盘操作次数, 提高了程序效率



缓冲字节输出流: BufferedOutputStream


字节缓冲流
    |_ BufferedInputStream     # 缓冲字节输入流
    |_ BufferedOutputStream    # 缓冲字节输出流

字符缓冲流
    |_ BufferedReader          # 缓冲字符输入流
    |_ BufferedWriter          # 缓冲字符输出流
   
java.io.BufferedOutputStream类: 缓冲字节输出流
    // 构造方法
    BufferedOutputStream(OutputStream out): 使用基本流创建一个缓冲字节输出流
    BufferedOutputStream(OutputStream out, int size): 使用基本流创建一个缓冲字节输出流, 设置缓冲区大小
   
BufferedOutputStream使用步骤:
    1.创建FileOutputStream对象, 构造方法中绑定要输出的目的地
    2.创建BufferedOutputStream对象, 构造方法中传递FileOutputStream对象
    3.使用BufferedOutputStream对象中的方法 write(), 把数据写入到内部缓冲区中
    4.使用BufferedOutputStream对象中的方法 flush(), 把内部缓冲区中的数据,刷新到文件中
    5.释放资源(会先调用flush方法刷新数据, 第4步可以省略)
        



缓冲字节输入流: BufferedInputStream


java.io.BufferedInputStream类: 缓冲字节输入流
    // 构造方法
    BufferedInputStream(InputStream in): 使用基本流创建一个缓冲字节输入流
    BufferedInputStream(InputStream in, int size): 使用基本流创建一个缓冲字节输入流, 设置缓冲区大小
   
使用步骤:
    1.创建FileInputStream对象, 构造方法中绑定要读取的数据源
    2.创建BufferedInputStream对象, 构造方法中传递FileInputStream对象
    3.使用BufferedInputStream对象中的方法 read(), 读取文件
    4.释放资源 close()
        



缓冲字符输出流: BufferedWriter


java.io.BufferedWriter类:
    // 构造方法
    BufferedWriter(Writer out): 使用基本流创建一个缓冲字符输出流
    BufferedWriter(Writer out, int size): 使用基本流创建一个缓冲字符输出流, 设置缓冲区大小
    // 特有方法
    void newLine(): 写入一个换行符, 换行符自动根据当前系统确定


缓冲字符输入流: BufferedReader


java.io.BufferedReader类: 缓冲字符输入流
    // 构造方法
    BufferedReader(Reader in): 使用基本流创建一个缓冲字符输入流
    BufferedReader(Reader in, int size): 使用基本流创建一个缓冲字符输入流, 设置缓冲区大小
    // 特有方法
    String readLine(): 一次读一行字符串, "不包含换行符". 读到文件末尾返回null


练习: 文本排序


需求:
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

将以上文本按照序号排序, 写入到当前模块下的out.txt中

说明:
    Map key 不可重复
    HashMap 哈希表 HashSet 123456.....10877 10876 10878
    TreeMap 红黑树, key按大小排序

分析:
    序号和文本是一对一关系: 用HashMap集合
        HashMap集合底层哈希表, key是存取无序的, 但是对于整数的key, 比较小的数字会自动按大小排序
    读取文件:
        一次读一行: 用
        拆分序号和内容: 用 split("\\.")
    写到新文件:
        遍历Map, 获取key和value
        按照.拼接key和value
        将字符串写入文件, 并写入换行


代码:


public class Test {
    public static void main(String[] args) throws IOException {
        // 创建Map集合, 用来存储序号和一行文本
//       Map<String, String> map =new HashMap<>(); // HashMap只是一定范围内的可以排序
        Map<String, String> map = new TreeMap<>();   // 建议使用TreeMap实现真正的排序

        // 创建缓冲字符输入流对象, 用来按行读取内容
        BufferedReader br = new BufferedReader(new FileReader("day10\\in.txt"));
        // 循环读取
        String line;
        while ((line = br.readLine()) != null) {
           // 读到一行字符串, 先切割
           String[] split = line.split("\\.");
           // 将切割出来的序号和文本添加到map
           map.put(split[0], split[1]);
        }
        // 循环结束后, 文件中的内容都已经存入了map
        // 关闭输入流
        br.close();

        // 创建缓冲字符输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("day10\\out.txt"));
        // 遍历map, 取出键值对
        for (String key : map.keySet()) {
           String text = map.get(key);
           // 拼接格式
           String result = key + "." + text;
           // 拼接一行就写入一行
           bw.write(result);
           bw.newLine();
        }
        // 遍历结束后, 文件也都写完了, 释放资源
        bw.close();
   }
}


转换流


字符编码和字符集


编码: 字符 -> 字节  
解码: 字节 -> 字符
编码表: 字符和二进制数字的对应规则
   
字符集和编码表: 字符集包含编码表
    ASCII字符集
        ASCII编码表
    ISO-8859-1字符集:
        Latin-1: 拉丁字符. 没有中文. 每个字符由1个byte组成
    GB字符集
        GB2312编码表: 每个字符由2个byte组成
        GBK编码表: 每个字符由2个byte组成
        GB18030编码表: 每个字符由1, 2, 4个byte组成
    Unicode字符集
        UTF-8: ASCII字符占1个byte, 拉丁字符占2个byte, 中文占3个byte, Unicode辅助字符占4个byte
        UTF-16
        UTF-32
    ANSI: 表示使用系统默认编码表


乱码问题:FileReader读取GBK编码


乱码原因: 读写编码不一致
乱码:
    ??? 找不到字符
    空格 看不见   找不到字符
    饕餮 能找到字符, 但是找错了


乱码原理: 字符流和转换流的关系


java.io.Reader
    |_ InputStreamReader: 转换字符输入流   
        |_ FileReader                     
java.io.Writer
    |_ OutputStreamWriter: 转换字符输出流            
|_ FileWriter                     


OutputStreamWriter类介绍及使用
java.io.OutputStreamWriter类: 输出转换流. 字符流通往字节流的桥梁
    // 构造方法
    OutputStreamWriter(OutputStream out): 使用默认编码表创建转换流
    OutputStreamWriter(OutputStream out, String charsetName): 使用指定编码表创建转换流
   
         写数据: 字符流 --------------------------> 字节流



InputStreamReader类介绍及使用


java.io.InputStreamReader类: 输入转换流. 字节流通往字符流的桥梁
    // 构造方法
    InputStreamReader(InputStream in): 使用默认编码表创建转换流
    InputStreamReader(InputStream in, String charsetName): 使用指定编码表创建转换流  

            读数据: 字符流 <---------- 字节流   
转换流小结:
FileReader = InputStreamReader + FileInputStream, 按照IDEA中UTF-8将读取的字节转换为字符
FileWriter = OutputStreamWriter + FileOutputStream, 按照IDEA中UTF-8将写出的字符转换为字节
转换流InputStreamReader, OutputStreamWriter在读写时, 可以指定按照哪个编码表来进行转换
所以:
字符流 = 编码表 + 字节流


序列化流(对象流)


序列化和反序列化概述


序列化: 内存中的对象转换为字节序列, 以流的方式写入到磁盘的文件中
    对象转字节
反序列化: 磁盘文件中的字节序列, 以流的方式读取到内存中变成对象
    字节转对象

通过序列化流, 我们可以将内存中的数据方便的存储到磁盘上, 下次程序启动后也能从磁盘读取恢复之前的对象状态

OutputStream
    |_ ObjectOutputStream类: 对象字节输出流
InputStream
    |_ ObjectInputStream类: 对象字节输入流


对象序列化流: ObjectOutputStream


java.io.ObjectOutputStream类: 对象字节输出流
    // 构造方法
    ObjectOutputStream(OutputStream out)
    // 特有成员方法
    void writeObject(Object obj): 将对象写出
  
注意:
    被读写的对象的类, 必须实现 java.io.Serializable 接口, 否则会抛出 NotSerializableException


对象反序列化流: ObjectInputStream


java.io.ObjectInputStream类: 对象字节输入流
    // 构造方法
    ObjectInputStream(InputStream in)
    // 特有成员方法
    Object readObject(): 读取对象
   


transient瞬态关键字: 避免属性序列化


static 修饰的成员变量属于类不属于对象, 所以不能序列化
transient 修饰的成员变量, 不能被序列化

如果对象的某个属性不希望被序列化, 可以使用transient修饰, 这样就不会被对象流写到文件中


InvalidClassException异常: 原因和解决方案


serialVersionUID序列号的作用:
    序列化操作时, 会根据类生成一个默认的 serialVersionUID 属性, 写入到文件中.
    该版本号是根据类中成员计算出来的一个数值. 当类的成员发生改变时, 序列版本号也会发生变化
    当代码中的类和读取的对象序列版本号不一致时, 就会抛出InvalidClassException

为了避免这种问题, 我们可以手动写死这个序列号, 这样无论成员如何变化, 序列版本号都是一样的


补充:


序列化流的应用场景:
    以后我们会用到"缓存", 缓存就是一些数据, 是存储在内存中的, 比如存储某个用户登录的状态
    而内存中的数据, 服务器一重启, 数据就没了, 所以我们需要在服务器重启前, 将内存中的数据写入到磁盘保存
    这时就可以用 序列化流
    服务器重启后, 先通过 反序列化流 读取到内存, 这样可以保持之前的状态


打印流


打印流PrintStream:概述和使用


PrintStream特点:
    1. 只有输出流, 没有输入流
    2. PrintStream不会抛出IOException
    3. 有特殊方法 print(), println(), 可以输出任意类型的值

java.io.PrintStream类: 字节打印流
    // 构造方法
    PrintStream(File file): 创建字节打印流, 输出到一个文件
    PrintStream(OutputStream out): 创建字节打印流, 输出到一个字节输出流
    PrintStream(String fileName): 创建字节打印流, 输出到一个文件路径
   
注意事项:
    如果用 write(  ) 方法, 会查编码表  
    如果用 print(  ), println( ), 则原样输出  
   
  java.lang.System类:
    // 静态方法
    static void setOut(PrintStream out): 设置System.out的输出目的地为    参数的打印流



day11 网络编程
网络编程基础

软件结构介绍
C/S结构:
    全称为Client/Server结构, 是指 客户端 和 服务器 结构
    常见程序有QQ, 迅雷等软件

B/S结构:
    全称为Browser/Server结构, 是指 浏览器 和 服务器 结构
    常见浏览器有IE, 谷歌, 火狐等
   

网络编程: 在一定的"协议"下, 实现两台计算机的通信的程序



网络通信协议

网络通信协议:
    通信协议是计算机必须遵守的规则, 只有遵守这些规则, 计算机之间才能进行通信.
    协议中对数据的传输格式, 传输速率, 传输步骤等做了统一规定, 通信双方必须同时遵守, 最终完成数据交换
    (类似于一种语言, 语法语速做了规定)

TCP/IP协议:
    Transmission Control Protocol/Internet Protocol, 传输控制协议/因特网互联协议.
    它定义了计算机如何连入因特网, 以及数据如何在它们之间传输的标准. 它的内部包含一系列的用于处理数据通信的协议, 并采用了4层的分层模型, 每一层都呼叫它的下一层所提供的协议来完成自己的需求


网络通信协议分类

UDP: User Datagram Protocol, 用户数据报协议
    特点:
        1. 无连接的不可靠协议
        2. 数据按包发送, 64K一个包
        3. 速度快效率高, 容易丢包
    用于视频直播, 网络电话

TCP: Transmission Control Protocol, 传输控制协议
    特点:
        1. 需要建立连接的可靠协议  
        2. 数据传输无大小限制
        3. 速度慢效率低   重发机制
    用于文件下载, 浏览网页
   
TCP通信的三次握手: TCP协议中, 在发送数据的准备阶段, 客户端与服务器之间的三次交互, 以保证连接的可靠
    1. 客户端向服务端发送验证信息, 等待服务器确认
    2. 服务端收到验证信息后, 回复客户端验证信息, 同时发送自己的一条验证信息
    3. 客户端收到服务端回复的信息, 确认自己之前发的信息无误, 并再次向服务器发回服务端的验证信息

网络编程三要素: IP地址

网络编程三要素:
    1. 通信协议  
    2. IP地址
    3. 端口号
        
IP地址: 互联网协议地址(Internet Protocol Address). 是网络中计算机的唯一标识
    版本:
        IPv4: 192.168.1.2
        IPv6: ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
    特殊的IP地址: "127.0.0.1", "localhost", 都代表自己的电脑


常用DOS命令:
    // 查看自己电脑的IP地址
    ipconfig
    // 查看是否能连接到指定IP地址
    ping IP地址

网络三要素: 端口号

什么是端口号

端口号: 计算机中进程的唯一标识
    端口号的取值范围: 0~65535的整数, 其中0~1024不建议使用
   
注意:
    通信的两端是2个计算机中的2个程序在相互通信, 所以2个程序都要有端口号. 端口号可以相同, 也可以不同, 相互之间能识别就行

域名: 便于记忆的主机名称, 可以解析到某个IP地址, 这样通过域名就能找到对应的IP地址

TCP通信

TCP通信程序概述()

java.net.ServerSocket类: TCP服务端
    // 构造方法
    ServerSocket(int port): 创建一个TCP服务端, 并监听指定端口
    // 成员方法
    Socket accept(): 监听数据, 会阻塞. 收到数据后返回Socket对象
    void close(): 关闭服务端ServerSocket

java.net.Socket类: TCP客户端
    // 构造方法
    Socket(String ip, int port): 创建TCP客户端对象
    // 成员方法
    OutputStream getOutputStream(): 获取输出流对象, 用于发送数据
    InputStream getInputStream(): 获取输入流, 用于接收数据
    void shutdownOutput(): 关闭输出流, 告知服务端数据发送完毕
    void close(): 关闭客户端Socket

TCP严格区分为 客户端(Client) 与 服务端(Server)

两端通信时步骤:
    1. 服务端程序需要先启动, 等待客户端的连接
    2. 客户端主动连接服务器端, 连接成功才能通信. 服务端不可以主动连接客户端

两端之间以 "IO字节流" 进行通信
一个服务端可以和多个客户端同时通信

TCP通信方式概述()

服务端(ServerSocket)可以通过 accept() 方法等待一个客户端(Socket)主动连接, 从而得到一个客户端对象(Socket), 来识别不同的客户端

服务端(ServerSocket)没有IO流, 是通过获取到"每个客户端对象(Socket)的IO流对象"来进行通信的.
    使用"客户端的InputStream"读取客户端发来的数据
    使用"客户端的OutputStream"向客户端回写数据

TCP程序: 客户端代码实现
  
实现步骤:
    1.创建一个客户端对象 Socket, 构造方法传入服务器的IP地址和端口号
    2.使用Socket对象中的方法 getOutputStream() 获取网络字节输出流OutputStream对象
    3.使用网络字节输出流OutputStream对象中的方法 write(), 给服务器发送数据
    4.使用Socket对象中的方法 getInputStream() 获取网络字节输入流InputStream对象
    5.使用网络字节输入流InputStream对象中的方法 read(), 读取服务器回写的数据
    6.释放资源(Socket)
        
注意:
    1.客户端和服务器端进行交互, 必须使用Socket中提供的网络流, 不能使用自己创建的流对象
    2.当我们创建客户端对象Socket的时候, 就会去请求服务器和服务器经过3次握手建立连接通路
        这时如果服务器没有启动, 那么就会抛出异常ConnectException
        如果服务器已经启动, 那么就可以进行交互了
        
    // 创建客户端Socket对象, 指定要连接的服务端的IP和端口号
    Socket socket = new Socket("127.0.0.1", 8888);

TCP程序: 服务器端代码实现

   
服务器的实现步骤:
    1.创建服务器ServerSocket对象并指定服务器占用的端口号
    2.使用ServerSocket对象中的方法 accept(), 获取到请求的客户端对象Socket
    3.使用Socket对象中的方法 getInputStream() 获取网络字节输入流InputStream对象
    4.使用网络字节输入流InputStream对象中的方法 read(), 读取客户端发送的数据
    5.使用Socket对象中的方法 getOutputStream() 获取网络字节输出流OutputStream对象
    6.使用网络字节输出流OutputStream对象中的方法 write(), 给客户端回写数据
    7.释放资源(Socket,ServerSocket)
        
    // 创建服务端ServerSocket对象, 并指定自己监听的端口
    ServerSocket server = new ServerSocket(8888);
    // 调用accept方法等待客户端连接
    Socket socket = server.accept();

     // 客户端一般不关闭

文件上传案例

文件上传案例: 客户端代码实现


边读边发
实现步骤:
    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)

文件上传案例: 服务器端代码实现

边收边写
实现步骤:
    1.创建一个服务器ServerSocket对象,和系统要指定的端口号
    2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
    3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
    4.判断d:\\upload文件夹是否存在,不存在则创建
    5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
    6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件
    7.使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
    8.使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
    9.使用网络字节输出流OutputStream对象中的方法write,给客户端回写"上传成功"
    10.释放资源(FileOutputStream,Socket,ServerSocket)

文件上传案例: 阻塞问题

java.net.Socket类: TCP客户端
    void shutdownOutput(): 关闭输出流, 告知服务端数据发送完毕

文件上传案例: 优化(命名, 循环, 多线程)
目前服务端有以下问题:
        1. 上传的图片因为写入文件名相同, 每次都会被覆盖, 如何解决?
        2. 上传一个文件服务端就结束了, 如何让服务端不停止, 一直接收文件上传
        3. 在同一个线程读大文件可能会比较慢, 能否利用多线程提高程序效率

解决方案:
    1. 上传文件的命名规则: 域名+毫秒值+随机数
    2. 将serverSocket.accept()直到最后的操作放入死循环中, 服务器就可以一直接收文件上传
    3. 当serverSocket.accept()得到Socket客户端对象后的操作, 全都放在子线程中执行

模拟B/S案例

模拟Web服务器: 分析
浏览器访问网站所看到的页面, 实际是服务器返回的数据
分析:
        浏览器输入网址, 回车: 发送请求 (就是客户端Socket去连接服务端, 并发送数据)
        浏览器显示页面: 是因为服务器返回了页面的代码, 浏览器按照代码显示

模拟Web服务器: 代码实现

具体步骤:
    将web目录赋值粘贴到当前模块中
    创建ServerSocket对象, 监听8080端口
    使用 while(true) 死循环, 在循环中调用ServerSocket对象的 accept() 方法, 获取客户端Socket对象
    在循环中创建线程并 start(), 在线程中传入Runnable的匿名内部类对象
    Runnable匿名内部类中编写读取请求返回页面的代码:
        1. 通过客户端socket获取InputStream, 经过转换流传递给BufferedReader, 便于读取一行字符串
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        2. 使用BufferedReader只读一行字符串即可, 然后按照空格切割字符串, 获取数组1索引的内容, 然后从1索引截取字符串到末尾
            即: GET /day11/web/index.html HTTP/1.1 获取其中的 day11/web/index.html
        3. 创建FileInputStream指向截取出来的字符串作为文件路径
        4. 通过客户端socket对象获取OutputStream, 先写入以下内容:
            OutputStream os = socket.getOutputStream();
            os.write("HTTP/1.1 200 OK\r\n".getBytes());
            os.write("Content-Type: text/html\r\n".getBytes());
            os.write("\r\n".getBytes());
        5. 然后使用一次读取一个byte[]数组方式, 通过刚才创建的FileInputStream对象和socket获取的OutputStream对象, 边读文件边写回客户端
        6. 关闭FileInputStream和socket, 释放资源





0 个回复

您需要登录后才可以回帖 登录 | 加入黑马