缓冲流,转换流,序列化流,网络编程

一,缓冲流

  能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等。这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,相当于是对基本流对象的一种增强。

1.1 概述
缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类:
①字节缓冲流: BufferedInputStream ,BufferedOutputStream
②字符缓冲流: BufferedReader ,BufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

1.2 字节缓冲流
构造方法:
public BufferedInputStream(InputStream in):创建一个 新的缓冲输入流。 
public BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流。
代码如下:
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));
1.2.1 字节输出流
构造函数:
BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
    BufferedOutputStream(OutputStream out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
参数:
   OutputStream out:字节输出流
      我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
      int size:指定缓冲流内部缓冲区的大小,不指定默认
使用步骤(重点)
   1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
   2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
   3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
   4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
   5.释放资源(会先调用flush方法刷新数据,第4部可以省略)

代码实例:

public class Demo01BufferedOutputStream {
    public static void main(String[] args) throws IOException {
        // 创建FileOutputStream对象,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream("a.txt");
        // 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        // 使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
        bos.write("数据写入到内部缓冲区中".getBytes());
        // 使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
        bos.flush();
        // 释放资源(会先调用flush方法刷新数据,第4部可以省略)
        bos.close();
    }
}
1.2.2 字节输入流
构造方法:
   BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
   BufferedOutputStream(OutputStream out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。

参数:
    OutputStream out:字节输出流
      我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
      int size:指定缓冲流内部缓冲区的大小,不指定默认
使用步骤(重点)
   1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
   2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
   3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
   4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
   5.释放资源(会先调用flush方法刷新数据,第4部可以省略)

代码实例:

public class Demo02BufferedInputStream {
    public static void main(String[] args) throws IOException {
        // 创建FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("a.txt");
        // 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
        BufferedInputStream bis = new BufferedInputStream(fis);
        //int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
        byte[] bytes =new byte[1024];//存储每次读取的数据
        int len = 0; //记录每次读取的有效字节个数
        while((len = bis.read(bytes))!=-1){
            System.out.println(new String(bytes,0,len));
        }
        // 释放资源
        bis.close();
    }
}

效率测试:
1.基本流,代码如下:

public static void main(String[] args) throws IOException {
    // 记录开始时间
    long start = System.currentTimeMillis();
    // 创建流对象
    FileInputStream fis = new FileInputStream("D:\\aaa\\vncviewer.exe");
    FileOutputStream fos = new FileOutputStream("D:\\bbb\\vncviewer_copy.exe");
    // 读写数据
    int b;
    while ((b = fis.read()) != -1){
        fos.write(b);
    }
    // 记录结束时间
    long end = System.currentTimeMillis();
    System.out.println("普通流复制时间:" + (end - start) + " 毫秒");
//关闭流
bos.close();
bis.close();
}

2.缓冲流,代码如下:
public static void main(String[] args) throws IOException {
    // 记录开始时间
    long start = System.currentTimeMillis();
    // 创建流对象
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\aaa\\vncviewer.exe"));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\bbb\\vncviewer_copy.exe"));
    // 读写数据
    int b;
    while ((b = bis.read()) != -1){
        bos.write(b);
    }
    // 记录结束时间
    long end = System.currentTimeMillis();
    System.out.println("缓冲流复制时间:" + (end - start) + " 毫秒");
    //关闭流
    bos.close();
    bis.close();
}
1.3 字符缓冲流
构造方法:
public BufferedWriter(Writer out):创建一个新的缓冲输出流。
public BufferedReader(Reader in):创建一个新的缓冲输入流。 

代码如下:

// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
1.3.1 字符缓冲输出流
构造方法:
     BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。
     BufferedWriter(Writer out, int size):创建一个使用给定大小输出缓冲区的新缓冲字符输出流。

参数:
    Writer out:字符输出流
    我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率
    int size:指定缓冲区的大小,不写默认大小

特有的成员方法:
    void newLine() 写入一个行分隔符。会根据不同的操作系统,获取不同的行分隔符
    windows:\r\n
    linux:/n
    mac:/r

使用步骤:
    1.创建字符缓冲输出流对象,构造方法中传递字符输出流
    2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
    3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
    4.释放资源

注意:System.out.println()换行方法,其实就是用的newLine方法。

代码实例:

public class Demo03BufferedWriter {
    public static void main(String[] args) throws IOException {
        //1.创建字符缓冲输出流对象,构造方法中传递字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("c.txt"));
        //2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
        for (int i = 0; i <10 ; i++) {
            bw.write("字符输出缓冲流");
            bw.newLine();
        }
        //3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
        bw.flush();
        //4.释放资源
        bw.close();
    }
}
1.3.2 字符缓冲输入流
构造方法:
     BufferedReader(Reader in)  创建一个使用默认大小输入缓冲区的缓冲字符输入流。
     BufferedReader(Reader in, int size)     创建一个使用指定大小输入缓冲区的缓冲字符输入流。

参数:
     Reader in:字符输入流
     们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
特有的成员方法:
    String readLine() 读取一个文本行。读取一行数据。一行被视为由换行符('\ n'),回车符('\ r')中的任何一个或随后的换行符终止。 

返回值:
    包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null

使用步骤:
    1.创建字符缓冲输入流对象,构造方法中传递字符输入流
    2.使用字符缓冲输入流对象中的方法read/readLine读取文本
    3.释放资源

代码实例:

public static void main(String[] args) throws IOException {
    // 创建流对象
    BufferedReader br = new BufferedReader(new FileReader("ddd.txt"));
    // 定义字符串,保存读取的一行文字
    String line;
    // 循环读取,读取到最后返回null
    while ((line = br.readLine())!=null) {
        System.out.println(line);
    }
    // 释放资源
    br.close();
}

二,转换流

2.1 字符编码和字符集
字符编码:
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制 数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照 某种规则解析显示出来,称为解码。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
字符编码 Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

字符集:
字符集 Charset :也叫编码表,是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符 集有ASCII字符集、GBK字符集、Unicode字符集等。

在这里插入图片描述
ASCII字符集 :
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显 示字符(英文大小写字符、阿拉伯数字和西文符号)。
基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits) 表示一个字符,共256字符,方便支持欧洲常用字符。

ISO-8859-1字符集: 
拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。 
ISO-8859-1使用单字节编码,兼容ASCII编码。

GBxxx字符集:
GB就是国标的意思,是为了显示中文而设计的一套字符集。
GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时, 就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文 的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这 就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了 21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。 
GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节 组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。

Unicode字符集 :
Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF32。最为常用的UTF-8编码。
UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
1.128个US-ASCII字符,只需一个字节编码。 
2.拉丁文等字符,需要二个字节编码。 
3.大部分常用字(含中文),使用三个字节编码。 
4.其他极少使用的Unicode辅助字符,使用四字节编码。

注意:如果编码不一样,会导致数据出现异常显示。

2.2 InputStreamReader类
转换流 java.io.InputStreamReader ,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法:
InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流。 
InputStreamReader(InputStream in, String charsetName) : 创建一个指定字符集的字符流。

参数:
InputStream in:字节输入流,用来读取文件中保存的字节
String charsetName:指定的编码表名称,不区分大小写,可以utf-8/UTF-8,gbk/GBK,…不指定默认使用UTF-8

使用步骤:
   1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
   2.使用InputStreamReader对象中的方法read读取文件
   3.释放资源

注意:
构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("b.txt") , "GBK");

指定编码读取
public static void main(String[] args) throws IOException {
    // 创建流对象,默认UTF8编码
    InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\abc\\c.txt"));
    // 创建流对象,指定GBK编码
    InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:\\abc\\c.txt") , "GBK");
    // 定义变量,保存字符
    int read;
    // 使用默认编码字符流读取,乱码
    while ((read = isr.read()) != -1) {
        System.out.print((char)read); // 好好学习,天天向上
    }
    isr.close();
    // 使用指定编码字符流读取,正常解析
    while ((read = isr2.read()) != -1) {
        System.out.print((char)read);
    }
    isr2.close();
}
2.3 OutputStreamWriter类
转换流 java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符 编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法:
OutputStreamWriter(OutputStream in) : 创建一个使用默认字符集的字符流。 
OutputStreamWriter(OutputStream in, String charsetName) : 创建一个指定字符集的字符流。

参数:
    OutputStream out:字节输出流,可以用来写转换之后的字节到文件中
    tring charsetName:指定的编码表名称,不区分大小写,可以utf-8/UTF-8,gbk/GBK,...不指定默认使用UTF-8

使用步骤:
    1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
    2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码)
    3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
    4.释放资源
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("a.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("b.txt"), "GBK");

指定编码写出
public static void main(String[] args) throws IOException {
    // 创建流对象,默认UTF8编码
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\a.txt"));
    // 写出数据
    osw.write("你好"); // 保存为6个字节
    osw.close();
    // 创建流对象,指定GBK编码
    OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:\\b.txt"),"GBK");
    // 写出数据
    osw2.write("你好");// 保存为4个字节
    osw2.close();
练习题:将GBK编码的文本文件,转换为UTF-8编码的文本文件。
分析:
1.指定GBK编码的转换流,读取文本文件。 
2.使用UTF-8编码的转换流,写出文本文件。
public static void main(String[] args) throws IOException {
    // 创建流对象,转换输入流,指定GBK编码
    InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\a.txt"), "GBK");
    // 创建流对象,转换输出流,默认utf8编码
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\b.txt"));
    // 读写数据,定义数组
    char[] cbuf = new char[1024];
    // 定义长度
    int len;
    // 循环读取
    while ((len = isr.read(cbuf))!=-1) {
        // 循环写出
        osw.write(cbuf,0,len);
    }
    // 释放资源
    osw.close();
    isr.close();
}

三,序列化

3.1 概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示
一个对象,该字节序列包含该对象的数据 、对象的类型和对象中
存储的属性等信息。字节序列写出到文件之后,相当于文件中持
久保存了一个对象的信息。反之,该字节序列还可以从文件中读
取回来,重构对象,对它进行反序列化。对象的数据 、 对象的
类型和对象中存储的数据信息,都可以用来在内存中创建对象。
看图理解序列化:

在这里插入图片描述
3.2 ObjectOutputStream类
java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

构造方法:
public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。

参数:
    OutputStream out:字节输出流

特有的成员方法:
    public void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。

使用步骤:
    1.创建ObjectOutputStream对象,构造方法中传递字节输出流
    2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
    3.释放资源

序列化操作:
1.一个对象要想序列化,必须满足两个条件: 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任 何状态序列化或反序列化,会抛NotSerializableException。该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient关键字修饰。
public class Employee implements java.io.Serializable {
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化
}

序列化1:
person类:
public class Person implements Serializable{ 
    private String name;
    public int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
测试类:
public class Demo01ObjectOutputStream {
    public static void main(String[] args) throws IOException {
        //1.创建ObjectOutputStream对象,构造方法中传递字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("aaa.txt"));
        //2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
        oos.writeObject(new Person("老王",28));
        //3.释放资源
        oos.close();
    }
}
序列化2:
transient关键字:瞬态关键字,被transient修饰成员变量,不能被序
列化。
static关键字:静态关键字,静态优先于非静态加载到内存中(静态
优先于对象进入到内存中),被static修饰的成员变量不能被序列
化的,序列化的都是对象
代码如下:
Person类:
public class Person implements Serializable{
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age;
    //public int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
序列化类:
public class Demo01ObjectOutputStream {
    public static void main(String[] args) throws IOException {
        //1.创建ObjectOutputStream对象,构造方法中传递字节输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\person.txt"));
        //2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
        oos.writeObject(new Person("小红",18));
        //3.释放资源
        oos.close();
    }
}
反序列化类:
public class Demo02ObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person p = null;
        // 创建反序列化流
        FileInputStream fileIn = new FileInputStream("D:\\person.txt");
        ObjectInputStream in = new ObjectInputStream(fileIn);
        // 读取一个对象
        p = (Person) in.readObject();
        // 释放资源
        in.close();
        fileIn.close();
        // 无异常,直接打印输出
        System.out.println("Name: " + p.getName()); //小红
        System.out.println("age: " + p.getAge()); // 0
    }
}
3.3 ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法:
public ObjectInputStream(InputStream in) :创建一个指定InputStream的ObjectInputStream。

参数:
    InputStream in:字节输入流
特有的成员方法:
    Object readObject() 从 ObjectInputStream 读取对象。

使用步骤:
    1.创建ObjectInputStream对象,构造方法中传递字节输入流
    2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件
    3.释放资源
    4.使用读取出来的对象(打印)

反序列化操作1:
如果能找到一个对象的class文件,我们可以进行反序列化操作,
调用 ObjectInputStream 读取对象的方法:public final Object readObject () : 读取一个对象。

代码如下:

public class Demo02ObjectInputStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Employee e = null;
        // 创建反序列化流
        FileInputStream fileIn = new FileInputStream("D:\\c.txt");
        ObjectInputStream in = new ObjectInputStream(fileIn);
        // 读取一个对象
        e = (Employee) in.readObject();
        // 释放资源
        in.close();
        fileIn.close();
        // 无异常,直接打印输出
        System.out.println("Name: " + e.name); // zhangsan
        System.out.println("age: " + e.age); // chengdu
    }
}

注意:对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException异常

反序列化操作2:
另外,当JVM反序列化对象时,能找到class文件,但是class文
件在序列化对象之后发生了修改,那么反序列化操 作也会失败,
抛InvalidClassException 异常。发生这个异常的原因如下:
①该类的序列版本号与从流中读取的类描述符的版本号不匹配 
②该类包含未知数据类型 
③该类没有可访问的无参数构造方法
Serializable 接口给需要序列化的类,提供了一个序列版本号。 
serialVersionUID 该版本号的目的在于验证序列化的对象和对应
类是否版本匹配。

代码实例:

public class Employee implements Serializable {
    // 加入序列版本号
    private static final long serialVersionUID = 1L;
    public String name;
    public String address;
    // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
    public int eid;
}
四、打印流
4.1 概述
平时我们在控制台打印输出,是调用 print 方法和 println 方法完
成的,这两个方法都来自于 java.io.PrintStream 类,该类能够方
便地打印各种数据类型的值,是一种便捷的输出方式。
4.2 PrintStream类
构造方法:
PrintStream(File file):输出的目的地是一个文件
PrintStream(OutputStream out):输出的目的地是一个字节输出流
PrintStream(String fileName) :输出的目的地是一个文件路径

代码如下:

PrintStream ps = new PrintStream("ps.txt");
改变打印流向:
System.out 就是 PrintStream 类型的,只不过它的流向是系统规
定的,打印在控制台上。不过,既然是流对象,我们就可以玩一
个"小把戏",改变它的流向。

代码实例:

public class PrintDemo {
    public static void main(String[] args) throws IOException {
        // 调用系统的打印流,控制台直接输出97
        System.out.println(97);
        // 创建打印流,指定文件的名称
        PrintStream ps = new PrintStream("D:\\d.txt");
        // 设置系统的打印流流向,输出到d.txt
        System.setOut(ps);
        // 调用系统的打印流,ps.txt中输出97
        System.out.println(97);
    }
}
注意:
    PrintStream 为其他输出流添加了功能,使它们能够方便地打
    印各种数据值表示形式。
PrintStream特点:
    1.只负责数据的输出,不负责数据的读取
    2.与其他输出流不同,PrintStream 永远不会抛出 IOException
    3.有特有的方法,print,println
        public void print(任意类型的值)
        public void println(任意类型的值并换行)
   4.如果使用继承自父类的write方法写数据,那么查看数据的时候
   会查询编码表 97->a
如果使用自己特有的方法print/println方法写数据,写的数据原样输
出 97->97

四,网络编程

1.1软件结构
C/S结构:全称为Client/Server结构,是指客户端和服务器结
构。常见程序有QQ、迅雷等软件。
B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结
构。常见浏览器有谷歌、火狐等。
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。
网络编程,就是在一定的协议下,实现两台计算机的通信的程
序。
1.2 网络通信协议
网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守
这些规则,计算机之间才能进行通信。这就好比在道路中行驶的
汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输
速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终
完成数据交换。
TCP/IP协议:传输控制协议/因特网互联协议( Transmission 
Control Protocol/Internet Protocol),是Internet最基本、最广泛
的协议。它定义了计算机如何连入因特网,以及数据如何在它们
之间传输的标准。它的内部包含一系列的用于处理数据通信的协
议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供
的协议来完成自己的需求。

在这里插入图片描述
1.3 协议分类
通信的协议还是比较复杂的, java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。
java.net 包中提供了两种常见的网络协议的支持:
UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。

TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。 
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。 
第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。

1.4 网络编程三要素
协议:计算机网络通信必须遵守的规则。

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亿个。
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。
为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
常用命令:
查看本机IP地址,在控制台输入:ipconfig
检查网络是否连通,在控制台输入:ping 空格 IP地址
本机IP地址:127.0.0.1 、 localhost

端口号:
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,
0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需
要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会
导致当前程序启动失败。
利用协议+ IP地址+ 端口号三元组合,就可以标识网络中的进程了,那么进
程间的通信就可以利用这个标识与其它进程进行交互。
注意:常见默认端口号,网络端口号80;数据库(mysql)端口号3306;Tomcat服务器8080
1.客户端:java.net.Socket 类表示。创建 Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。 
2.服务端:java.net.ServerSocket 类表示。创建 ServerSocket 对象,相当于开启一个服务,并等待客户端的连接。

参数:
String host:服务器主机的名称/服务器的IP地址
int port:服务器的端口号

成员方法:
   public InputStream getInputStream():返回此套接字的输入流。如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。关闭生成的InputStream也将关闭相关的Socket。 
public OutputStream getOutputStream():返回此套接字的输出流。如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。关闭生成的OutputStream也将关闭相关的Socket。 
public void close():关闭此套接字。一旦一个socket被关闭,它不可再使用。 关闭此socket也将关闭相关的InputStream和OutputStream 。 
public void shutdownOutput():禁用此套接字的输出流。 任何先前写出的数据将被发送,随后终止输出流。

实现步骤:
    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: Connection refused: 
    connect如果服务器已经启动,那么就可以进行交互了
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",6666);
        //2.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //3.使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
        os.write("连接服务器".getBytes());

        //4.使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //5.使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //6.释放资源(Socket)
        socket.close();
    }
}
2.3 ServerSocket类
ServerSocket 类:这个类实现了服务器套接字,该对象等待通过
网络的请求。

构造方法:
public ServerSocket(int port) :使用该构造方法在创建
ServerSocket对象时,就可以将其绑定到一个指 定的端口号上,
参数port就是端口号。

成员方法:
public Socket accept():侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。

服务器的实现步骤:
    1.创建服务器ServerSocket对象和系统要指定的端口号
    2.使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
    3.使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
    4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
    5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
    6.使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
    7.释放资源(Socket,ServerSocket)
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.创建服务器ServerSocket对象和系统要指定的端口号
        ServerSocket server = new ServerSocket(6666);
        //2.使用ServerSocket对象中的方法accept,获取到请求的客户端对象Socket
        Socket socket = server.accept();
        //3.使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        System.out.println(new String(bytes,0,len));
        //5.使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //6.使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
        os.write("获取到客户端信息".getBytes());
        //7.释放资源(Socket,ServerSocket)
        socket.close();
        server.close();
    }
}
2.4 简单的TCP网络程序
TCP通信分析图解 
1.服务端启动,创建ServerSocket对象,等待连接。 Socket client = new Socket("127.0.0.1", 6666); ServerSocket server = new ServerSocket(6666); 
2.客户端启动,创建Socket对象,请求连接。 
3.服务端接收连接,调用accept方法,并返回一个Socket对象。 
4.客户端Socket对象,获取OutputStream,向服务端写出数据。 
5.服务端Scoket对象,获取InputStream,读取客户端发送的数据
6.服务端Socket对象,获取OutputStream,向客户端回写数据。 
7.客户端Scoket对象,获取InputStream,解析回写数据。 
8.客户端释放资源,断开连接。
客户端向服务器发送数据
服务端:
public class ServerTCP {
    public static void main(String[] args) throws IOException, IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 通过socket 获取输入流
        InputStream is = server.getInputStream();
        // 创建字节数组
        byte[] b = new byte[1024];
        // 据读取到字节数组中.
        int len = is.read(b);
        // 解析数组,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        // 关闭资源.
        is.close();
        server.close();
    }
}
客户端:
public class ClientTCP {
    public static void main(String[] args) throws Exception {
        // 创建 Socket ( ip , port ), 确定连接地址和端口号
        Socket client = new Socket("localhost", 6666);
        // 获取流对象,输出流
        OutputStream os = client.getOutputStream();
        // 写出数据
        os.write("发送数据给服务器端~~~".getBytes());
        // 关闭资源
        os.close();
        client.close();
    }
}

服务器向客户端回写数据
服务端:
public class ServerTCP {
    public static void main(String[] args) throws IOException, IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 通过socket 获取输入流
        InputStream is = server.getInputStream();
        // 创建字节数组
        byte[] b = new byte[1024];
        // 据读取到字节数组中.
        int len = is.read(b);
        // 解析数组,打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        // 通过 socket 获取输出流
        OutputStream out = server.getOutputStream();
        // 回写数据
        out.write("发送数据给客户端~~~".getBytes());
        // 关闭资源.
        is.close();
        server.close();
    }
}
客户端:
public class ClientTCP {
    public static void main(String[] args) throws Exception {
        // 创建 Socket ( ip , port ), 确定连接地址和端口号
        Socket client = new Socket("localhost", 6666);
        // 获取流对象,输出流
        OutputStream os = client.getOutputStream();
        // 写出数据
        os.write("发送数据给服务器端~~~".getBytes());
        // 通过Scoket,获取 输入流对象
        InputStream in = client.getInputStream();
        // 读取数据数据
        byte[] b = new byte[100];
        int len = in.read(b);
        System.out.println(new String(b, 0, len));
        // 关闭资源
is.colse();
        os.close();
        client.close();
    }
}

练习题:修改代码,完成相互通信。即,在不主动关闭程序的情况下,客户端和服务器端可以一直通信(聊天)。

服务器端:
public class ServerTCP {
    public static void main(String[] args) throws IOException, IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        // 创建 ServerSocket对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
            // 接收连接 accept 方法, 返回 socket 对象.
        while (true) {
            Socket server = ss.accept();
            // 通过socket 获取输入流
            InputStream is = server.getInputStream();
            // 创建字节数组
            byte[] b = new byte[1024];
            // 据读取到字节数组中.
            int len = is.read(b);
            // 解析数组,打印字符串信息
            String msg = new String(b, 0, len);
            System.out.println(msg);
            // 通过 socket 获取输出流
            OutputStream out = server.getOutputStream();
            // 回写数据
            String s = new Scanner(System.in).next();
            out.write(s.getBytes());
        }
    }
}
客户端:
public class ClientTCP {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端 发送数据");
        while (true) {
            // 创建 Socket ( ip , port ), 确定连接地址和端口号
            Socket client = new Socket("localhost", 6666);
            // 获取流对象,输出流
            OutputStream os = client.getOutputStream();
            // 写出数据
            String s = new Scanner(System.in).next();
            os.write(s.getBytes());
            // 通过Scoket,获取 输入流对象
            InputStream in = client.getInputStream();
            // 读取数据数据
            byte[] b = new byte[100];
            int len = in.read(b);
            System.out.println(new String(b, 0, len));
        }
    }
}
文件上传实现:
服务器实现步骤:
    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)

客户端实现步骤:
    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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值