Java之IO流与网络编程

😀😀😀创作不易,各位看官点赞收藏.

Java之IO流与网络编程

1、File类

File类:表示一个文件或者文件夹对象。

public static void main(String[] args) {
    // "hello.txt" 是文件的存储路径,可以是相对路径也可以是绝对路径
    File file1 = new File("hello.txt");
    System.out.println(file1); // 打印文件的路径

    // 参数1:文件上一级目录,参数二:在目录下的子文件
    File file2 = new File("E:\\code\\idea\\study", "javaSE");
    System.out.println(file2);

    // 参数1:一个File对象,参数二:文件对象下的子文件
    File file3 = new File(file2, "hello.txt");
    System.out.println(file3);
}

File类常用方法:

public static void main(String[] args) {
    File file = new File("hello.txt");

    System.out.println(file.getAbsolutePath()); // 获取文件的绝对路径
    System.out.println(file.getPath()); // 获取初始化文件路劲参数
    System.out.println(file.getName()); // 获取文件名
    System.out.println(file.getParent()); // 获取初始化上一级文件的路径,没有返回null
    File parentFile = file.getParentFile(); // 获取初始化上一级文件,返回一个文件对象,没有返回null
    System.out.println(parentFile);
    System.out.println(file.length()); // 获取文件的长度(字节数)
    boolean flag = file.setLastModified(new Date(1022837994823L).getTime()); // 设置文件最新修改时间

    String[] files = file.list(); // 对于文件夹,获取这个文件夹同级的所有文件或文件夹名称
    for (String s : files) {
        System.out.println(s);
    }

    File[] files1 = file.listFiles(); // 对于文件夹,获取这个文件夹同级的所有文件或文件夹,并返回的是文件对象数组
    for (File file1 : files1) {
        System.out.println(file1);
    }

    File file1 = new File("E:\\hello.txt");
    //  将file移动到file1指定目录下可以指定新的名称,但是必须保证file在硬盘中存在,而file1不能在硬盘中存在、
    // 这种方式可以用来重命名文件
    System.out.println(file.renameTo(file1));
}
public static void main(String[] args) {

    File file = new File("hello.txt");

    System.out.println(file.isDirectory()); // 是否是文件夹
    System.out.println(file.isFile()); // 是否是文件,与文件夹2选1
    System.out.println(file.exists()); // 文件是否存在
    System.out.println(file.canRead()); // 文件是否可读
    System.out.println(file.canWrite()); // 文件是否可写
    System.out.println(file.isHidden()); // 文件是否隐藏

    // 上面的方法全部返回都是boolean
}

创建文件:创建后的文件保存到硬盘中。

public static void main(String[] args) throws IOException {

    File file1 = new File("test.txt");
    // 创建文件:先判断文件是否存在,如果不存在就可以创建,存在就不创建,并且是在硬盘中创建
    if(!file1.exists()){
        boolean newFile = file1.createNewFile();
        if (newFile){
            System.out.println("文件创建成功");
        }
    }

    File file2 = new File("demo");
    // 创建文件夹:先判断文件夹是否存在,如果不存在就可以创建,存在就不创建,并且是在硬盘中创建
    if (!file2.exists()){
        boolean mkdir = file2.mkdir(); // 如果file存在多级目录,如果上一级的目录不存在就不会创建文件
        if (mkdir){
            System.out.println("单文件夹创建成功");
        }
        boolean mkdirs = file2.mkdirs(); // 如果存在多级目录,即使上级目录不存在会帮你把上级目录也创建
        if (mkdirs){
            System.out.println("多级目录创建成功");
        }
    }
}

删除文件和文件夹:

public static void main(String[] args) {

    File file1 = new File("test.txt");
    // 删除文件
    if (file1.exists()){
        boolean delete = file1.delete();
        if (delete){
            System.out.println("文件删除成功");
        }
    }

    File file2 = new File("demo");
    // 删除文件夹
    if (file2.exists()){
        boolean delete = file2.delete(); // 删除的文件必须为空才能删除
        if (delete){
            System.out.println("空文件夹删除删除");
        }
    }
}

文件的删除不会经过回收站,会直接把文件删除了。

2、IO流

I/O流:Input/Output的缩写,用于处理设备之间的数据传输。如读写文件、网络传输等。Java程序中,对于数据的输入/输出都是以流的形式进行的。

I/O流分类:

  • 按照操作数据单位不同:字节流(8 bit)、字符流(16 bit,2个字节)。
  • 按照数据的流向不同:输入流(Input)、输出流(Output)。
  • 按照角色不同:节点流(直接作用在文件和程序上)、处理流(在现有流上进行包装后的流)。
抽象子类字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

image-20220523150958431

2.1、字符流

2.1.1、FileReader 文本文件输入流

read():读取一个字符,并返回读取字符的ascii整数,文件读取结束后返回的是-1,这种方式读取效率差。

public static void main(String[] args) {
    File file = new File("hello.txt");
    // FileReader 文件字符流,读取字符串型文件
    try (FileReader fileReader = new FileReader(file);){ // 打开输入流,如果文件不存在就会报异常
        int data;
        while ((data = fileReader.read()) != -1){  // 读取数据
            System.out.print((char) data);
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

read(char[] byte):把字符读取到一个char类型的数组,返回读取字符的长度,文件读取结束返回-1,这种方式读取效率较高。

public static void main(String[] args) {
    File file = new File("hello.txt"); 
    try(FileReader fileReader = new FileReader(file)){ // 打开输入流,如果文件不存在就会报异常
        char[] buffer = new char[1024]; // 读取的数据保存在这个数组中
        int len = -1;
        while ((len = fileReader.read(buffer)) != -1){
            for(int i=0;i<len;i++){ // 注意:这里的len不能写成buffer.length(),因为可能有一个读取的数据没有将buffer数组填满
                System.out.print(buffer[i]); // 遍历读取的数组
            }
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}
2.1.2、FileWriter 文本文件输出流
public static void main(String[] args) {
    File file = new File("test.txt");
    
    try ( FileWriter writer = new FileWriter(file);){ // 打开字符输出流,如果文件不存在就会创建一个新文件,如果文件存在就覆盖文件内容
        
        writer.write("你好世界,这是第一个测试\n"); // 向文件中填入数据
        writer.write("你好世界,这是第二个测试\n");
    }catch (IOException e){
        e.printStackTrace();
    }
}

FileWriter writer = new FileWriter(file,true);是FileWriter构造器的一个重载方法,第二个参数是true表示填入数据不会覆盖原来文件,而是在接着在文件中添加,默认是false。

2.1.3、文本文件复制
// 字符流进行文件复制
public static void main(String[] args) {
    File in = new File("hello.txt"); // 源文件对象
    File out = new File("hello1.txt"); // 复制后的目标文件对象

    try(
        FileReader reader = new FileReader(in); // 文件不存在报错
        FileWriter writer = new FileWriter(out); // 文件不存在就创建一个新文件
    ){
        char[] data = new char[1024];
        int len = -1;
        while ((len = reader.read(data)) != -1){ // 输入数据
            // 输出数据,输出长度为len
            writer.write(data,0,len);
        }
        System.out.println("文件复制成功");
    }catch(IOException e){
        e.printStackTrace();
    }
}

注意:
字符流不能来处理字节类型的文件,不管是输入和输出都会出现格式不正确导致乱码问题,例如图片、视频、音频、word等。

2.2、字节流

public static void main(String[] args) {
    File file = new File("hello.txt");

    // 字节流读取文本文件
    try (FileInputStream in = new FileInputStream(file)){ // 打开字节流,如果文件不存在就会报错

        byte[] data = new byte[5];
        int len = -1;
        while ((len = in.read(data)) != -1){ // 读取数据
            System.out.println(new String(data,0,len));
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

因为在UTF-8编码下,汉字占用三个字节,在读取的过程中可能存在将汉字分开读取导致汉字乱码,所以一般不用字节流来处理文本文件,像.txt、java、c、cpp等都是文本文件,字节流一般用来处理一些非文本文件,例如视频、音频等。

// 字节流来处理非文本文件,实现非文本文件的复制
public static void main(String[] args) {
    File io = new File("x.docx");
    File out = new File("y.docx");

    try(FileInputStream fis = new FileInputStream(io); // 打开字节输入流,文件不存在会报错
        FileOutputStream fos = new FileOutputStream(out); // 打开字节输出流,文件不存在会创建一个新文件
    ){
        byte[] data = new byte[10];
        int len = -1;
        while ((len = fis.read(data)) != -1){ // 读取文件字节数据
            fos.write(data,0,len); // 输出字节数据到指定文件
        }
        System.out.println("文件复制成功");
    }catch (IOException e){
        e.printStackTrace();
    }
}

总结:

  • 字符流只能用来处理文本文件,对于非文本文件不能使用。
  • 字节流用来处理非文本文件,也可以处理文本文件但是不能在内存中去查看处理内容可能存在乱码问题,建议不使用字节流去处理文本文件。

2.3、缓冲流

缓冲流:是处理流的一种,用于提高数据读取、写入的速度。字符缓冲流:BufferedReader、BufferedWriter;字节缓冲流:BufferedInputStream、BufferedOutputStream。一般在实际开发中使用缓冲流来处理数据。

// 使用 字节缓冲流 来复制文件
public static void main(String[] args) {
    File is = new File("-编译原理上机报告3.docx");
    File out = new File("-编译原理上机报告5.docx");

    try (FileInputStream fis = new FileInputStream(is);
         FileOutputStream fos = new FileOutputStream(out);
         BufferedInputStream bfis = new BufferedInputStream(fis); // 使用字节流来打开缓冲流
         BufferedOutputStream bfos = new BufferedOutputStream(fos);
        ){
        // 使用缓冲区读取和写入数据
        byte[] data = new byte[1024];
        int len = -1;
        while ((len = bfis.read(data)) != -1){ // 缓冲流读取数据
            bfos.write(data,0,len); // 缓冲流写入数据
            // 清空缓冲区读取到的数据,将全部读取到的数据写入的文件中,执行write()方法会自动执行这个方法,可以不写
            // bfos.flush(); 
        }
        System.out.println("文件复制成功");
    }catch (IOException e){
        e.printStackTrace();
    }
}
// 使用 字符缓冲流 复制文本文件
public static void main(String[] args) {
    File is = new File("hello.txt");
    File out = new File("hello2.txt");

    try(FileReader fis = new FileReader(is);
        FileWriter fos = new FileWriter(out);
        BufferedReader bufferedReader = new BufferedReader(fis);
        BufferedWriter bufferedWriter = new BufferedWriter(fos);
       ){
        // 方式一:
        //            char[] data = new char[1024];
        //            int len = -1;
        //            while ((len = bufferedReader.read(data)) != -1){
        //                bufferedWriter.write(data,0,len);
        //            }

        // 方式二:
        String str;
        while ((str = bufferedReader.readLine()) != null){ // 读取一行字符串,文件读取完毕,方法返回 null
            bufferedWriter.write(str); // 写入一行写入的字符串,不包含换行符
            bufferedWriter.newLine(); // 插入换行符
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

2.4、转换流

转换流:提供了字节流与字符流之间的转换。常用转换流:InputStreamReader、OutputStreamWriter。转换流一般用于修改文本文件的编码格式。

// 转换流:InputStreamReader、OutputStreamWriter
public static void main(String[] args) {

    File is = new File("hello.txt");
    File out = new File("hello4.txt");
    try(FileInputStream fis = new FileInputStream(is);  // 打开输入字节流
        InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8); // 将输入字节流装换成字符流,默认的解码格式是UTF-8,编码格式取决于创建文件时的编码格式
        BufferedReader bufferedReader = new BufferedReader(isr); // 输入缓冲流
        FileOutputStream fos = new FileOutputStream(out);  // 打开输出字节流
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos,"gbk"); // 将字节输出流转换成字符输出流,默认的格式是GBK,也可以指定一个编码格式,之前是UTF-8可以编码成其它格式
        BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter); // 输出缓冲流
    ){
        char[] data = new char[5];
        int len = -1;
        while ((len = bufferedReader.read(data)) != -1){
            System.out.print(new String(data,0,len));
            bufferedWriter.write(data,0,len);
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

2.5、其它流

标准输入输出流:标准输入流(System.in),默认从键盘中输入;标准输出流(System.out),默认从控制台输出。in和out和System的属性,in(InputStream)、out(PrintStream)。

// 使用标准输入流从键盘读取数据
public static void main(String[] args) {
    try (InputStreamReader streamReader = new InputStreamReader(System.in);   // 使用转换流将标准输入流转换成字符流
         BufferedReader reader = new BufferedReader(streamReader);)    // 使用字符缓冲流
    {
        String str;
        while ((str = reader.readLine()) != null && str.length()>0){ // 读取字符串
            System.out.println(str); // 标准输出流打印数据
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

打印流:PrintStream、PrintWriter这两个打印流。

// 修改默认输出流为指定的打印流
public static void main(String[] args) {
    try(FileOutputStream fos = new FileOutputStream(new File("hello5.txt"),true); // 文件字节流
        PrintStream printStream = new PrintStream(fos,true); // 创建一个打印流
    ){
        System.setOut(printStream); // 将标准输出流设置为对应文件打印流,这样使用System.out会直接输出到文件中
        System.out.println("我是新的打印流,不是默认的控制台打印流");
        System.out.println("Hello,world!!!!");
    }catch (IOException e){
        e.printStackTrace();
    }
}

数据流:DataInputStream、DataOutputStream,为了方便操作Java中的基本数据类型,可以使用数据流。

// 使用数据流进行文件的读写
public static void main(String[] args) {
    try(FileOutputStream fos = new FileOutputStream("hello6.txt");
        DataOutputStream dataOutputStream = new DataOutputStream(fos);
        FileInputStream fis = new FileInputStream("hello6.txt");
        DataInputStream dataInputStream = new DataInputStream(fis);
    ){
        String name = "张三";
        int age = 18;
        boolean isMale = true;

        // 写入到文件中的数据会存在乱码的情况,只能通过数据流读取到内存中才能正常显示
        dataOutputStream.writeUTF(name);  // 使用数据流将基本数据类型的写入到文件中
        dataOutputStream.flush(); // 清空缓冲区
        dataOutputStream.writeInt(age);
        dataOutputStream.flush();
        dataOutputStream.writeBoolean(isMale);
        dataOutputStream.flush();

        // 注意这个读入的顺序和数据写入的顺序需要保存一致,不然读取的数据会乱码
        System.out.println(dataInputStream.readUTF()); // 使用数据流将文件的中数据读取到内存中
        System.out.println(dataInputStream.readInt());
        System.out.println(dataInputStream.readBoolean());
    }catch (IOException e){
        e.printStackTrace();
    }
}

对象流:ObjectInputStream、ObjectOutputStream,用于存储和读取基本数据类型和对象的处理流。它可以将Java对象写入到数据源中,也能把数据从数据源中还原出来。

// 使用对象流将一个可序列化对象持久到磁盘中(也可以通过网络进行传输)
public static void main(String[] args) {
    try(FileOutputStream fos = new FileOutputStream(new File("data.dat"));
        ObjectOutputStream oos = new ObjectOutputStream(fos); // 创建一个对象流
    ){
        // 序列化过程
        oos.writeObject(new String("你好,世界!!!")); // String是一个可序列化对象
        oos.flush();
    }catch (IOException e){
        e.printStackTrace();
    }
}
// 从磁盘中读取一个可序列化的对象,这个过程称为反序列化(也可以从网络中读取)
public static void main(String[] args) {
    try(FileInputStream fis = new FileInputStream("data.dat");
        ObjectInputStream objectInputStream = new ObjectInputStream(fis); // 使用对象输入流
       ){
        Object object = objectInputStream.readObject(); // 从对象流中读取一个对象
        Student student = (Student) object; // 将对象强制装换成student
        System.out.println(student);
    }catch (IOException | ClassNotFoundException e){
        e.printStackTrace();
    }
}

随机存取文件流:RandomAccessFile,这个类既可以读也可以写。程序可以直接跳转到文件的任意地方进行读写。支持只访问文件的部分内容也可以向文件中追加内容。

// 使用RandomAccessFile读写文件
public static void main(String[] args) {
    /*
            参数一:是一个读取的文件,需要存在
            参数二:指定流的模式:
                r:只能读
                rw:能读能写
                rwd:能读能写,并且数据同步到文件中(在读写过程中出现异常,修改的的内容会保存下来)
                rws:能读能写,并且同步数据的元数据
         */
    try(RandomAccessFile randomAccessFile = new RandomAccessFile(new File("photo.jpg"), "r");
        RandomAccessFile rw = new RandomAccessFile(new File("photo1.jpg"),"rw");
       ){
        byte[] data = new byte[1024];
        int len = -1;
        while ((len = randomAccessFile.read(data)) != -1){ // 读取数据
            rw.write(data,0,len);    // 写入数据
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}

注意:

  • 如果RandomAccessFile写入的文件不存在就会新建一个文件并写入。
  • 如果RandomAccessFile写入的文件存在,它会从开头去覆盖文件内容(一个一个挨着覆盖,没有覆盖的数据会保留下来),而不是直接覆盖文件。

在RandomAccessFile对象中存在一个游标指针,默认指针开始位置下标是0。通过getFilePointer()获取游标的位置,seek()来设置游标的文件,这样就可以实现文件指定位置的读写。

2.6、序列化和反序列化

序列化:将内存中的Java对象转换成与平台无关的二进制流,可以将这个二进制数据流持久化到硬盘中或者通过网络的方式将二进制流传送到另一个网络节点。通过ObjectOutputStream进行序列化。

反序列化:当程序获取了一个序列化后的字节数据流,可以从流中将数据恢复到对象的过程。通过ObjectInputStream进行反序列化。

序列化的好处在于可以将一个可序列化的对象转换成字节数据,使其在保存或传输过程中被还原。
// 实现可序列化的要求:
// 1、实现Serializable接口,然后添加一个序列版本号,每一个类的版本号需要都不一样
// 2、还需要保证所有的属性都可以序列化,基本数据类型默认课序列化的
// 3、static和transient修饰的成员变量不能被序列化
public class Person implements Serializable {
    
    // 序列版本号
    public static final long serialVersionUID = 62874283749234L;
    
}

序列版本号:

每一个可序列化的类中都需要指定一个序列版本号。简单来说Java序列化机制是通过判断类的序列版本号来验证版本是否一致,在进行反序列化时JVM会将字节流中的版本号和类的版本号进行对比,如果一致就可以进行反序列化,如果一致就不能反序列化就会出现异常。 如果没有指定版本号的话,在Java运行环境时会自定生成一个,但是如果类的结构发生变化这个序列版本号就会发生变化,就会在反序列化时不能找到对应的类。

3、网络编程

​ 在Java中提供了网络编程类库,底层实现被隐藏在了Java系统中由JVM控制。程序员面对的是一个统一的编程环境,可以直接使用Java封装好的API。

IP地址:唯一标识互联网上的一台主机。在Java中InetAddress这个类就是表示一个IP地址,每一个对象对应一个IP地址。IP分为IPv4和IPv6。IPv4是一个32位4个字节组成的,总共能有42亿个;IPv6是一个128位16字节组成的,总共能有2^128次方个,可以说是永远不会用完的。

// 参数可以是域名,也可以是一个ip地址字符串
InetAddress IP = InetAddress.getByName("www.yujiangg.com");
// 获取ip的一个字节数组
System.out.println(inetAddress.getAddress());
// 返回主机的公网ip地址
System.out.println(inetAddress.getHostAddress());
// 返回主机的规范名称的,相当于ip
System.out.println(inetAddress.getCanonicalHostName());
// 获取主机的域名
System.out.println(inetAddress.getHostName());

// 获取本机的一个IP对象
InetAddress localHost = InetAddress.getLocalHost();

端口:标识每一台计算机上的不同继承,是一个16位的整数(0~65535)。端口号不能冲突,多个进程不能使用同一个端口号。

  • 公有端口(0~1023):这个端口是会被内置的程序使用,一般不能使用这些端口。HTTP80、FTP21、Telnet23。
  • 注册端口(1024~49151):这些端口就是提供给用户使用的,表示程序。Tomcat8080、MySQL3306、Oracle1521。
  • 动态端口(后面的端口):也不要使用这里面的端口号。

Windows 查看端口号命令:

netstat -ano # 查询所有端口
netstat -ano | findstr "端口号"  # 查看指定端口,| 是筛选符号
tasklist|findstr "端口号" # 查看这个端口号下的程序

Socket网络套接字:使用IP地址和端口号可以组合成一个网络的套接字,通过这个套接字就可以实现网络的通信。

// 第一个参数是IP,第二个参数是端口号
InetSocketAddress socket = new InetSocketAddress(InetAddress.getByName("www.baidu.com"), 8080);
// 获取ip的一个字节数组
System.out.println(socket.getAddress());
// 获取主机名
System.out.println(socket.getHostName());
// 获取端口号
System.out.println(socket.getPort());

网络协议:计算机网络中实现通讯必须有一定的约定,对速率、传输代码、代码结构、传输控制步骤、出错控制等标准约定。

image-20220526192420241

3.1、TCP的网络编程

服务端代码:

// 服务器端
public static void main(String[] args) {
    int port = 8889;

    try(ServerSocket serverSocket = new ServerSocket(port); // 通过一个端口号创建一个服务器socket对象
        Socket accept = serverSocket.accept(); // 监听客户端连接,这是一个阻塞方法,如果客户端一直没有连接,程序会一直停留在这里
        InputStream is = accept.getInputStream();
        ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); // 这个流用于将输入流收到的字节数据保存起来,然后一起输出
       ){
        byte[] data = new byte[20];
        int len = -1;
        while ((len = is.read(data)) != -1) {
            // 写入暂存数据流
            arrayOutputStream.write(data, 0, len);
        }

        // 获取到客户端的IP对象
        InetAddress client = accept.getInetAddress();

        // 将接收到的数据输出
        System.out.println(client.getHostAddress()+":"+arrayOutputStream.toString());
    }catch (IOException e){
        e.printStackTrace();
    }
}

客户端代码:

// 客户端
public static void main(String[] args){
    int port = 8889; // 服务器的端口号

    try (Socket socket = new Socket(InetAddress.getByName("172.24.31.161"),port); // 通过服务器的IP地址和端口号获取一个socket套接字
         OutputStream outputStream = socket.getOutputStream(); // 获取输出流
         BufferedOutputStream buffer = new BufferedOutputStream(outputStream);
        ){
        String data = "你好服务器,我是客户端,收到请回话!!!!";
        buffer.write(data.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}
3.2、TCP的文件传输

服务器端:接收文件。

// 文件接收服务器端
public static void main(String[] args) {
    int port = 8889; // 端口

    try(ServerSocket serverSocket = new ServerSocket(port);
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); // 使用输入缓冲流
        FileOutputStream out = new FileOutputStream(new File("test1.png"));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out); // 输出缓冲流
       ){
        byte[] data = new byte[1024];
        int len = -1;
        while ((len = bufferedInputStream.read(data)) != -1){
            bufferedOutputStream.write(data,0,len);
        }
        String address = socket.getInetAddress().getHostAddress();
        System.out.println("成功接收来自 "+address+" 的文件!");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

客户端:发送文件。

// 客户端发送文件
public static void main(String[] args) {
    int port = 8889;

    try(Socket socket = new Socket(InetAddress.getByName("localhost"),port);
        FileInputStream inputStream = new FileInputStream(new File("test.png")); // 读取需要发送的文件的输入流
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        OutputStream outputStream = socket.getOutputStream();
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); // 发送文件的缓冲输出流
       ){
        byte[] data = new byte[1024];
        int len = -1;
        while((len = bufferedInputStream.read(data)) != -1){ // 读取
            bufferedOutputStream.write(data,0,len); // 发送
        }
        System.out.println("文件成功发送给服务器!");
    } catch (IOException e) {
        e.printStackTrace();
    }
}
3.3、UDP的网络编程

UDP是不需要连接,是直接发送数据报给某个用户,是不需要连接服务器的,类似QQ、微信等。

UDP数据发送端:

// UDP发送数据端
public static void main(String[] args) {
    int port = 8080;

    try(DatagramSocket socket = new DatagramSocket();) {
        String message = "你好,世界!";
        byte[] data = message.getBytes(StandardCharsets.UTF_8); // 发送的数据
        InetAddress address = InetAddress.getByName("localhost"); // 发送的IP地址
        // 发送的数据报,需要指定一个字节数组,数据长度,发送的IP地址,对方接收的端口号
        DatagramPacket packet = new DatagramPacket(data, 0, data.length, address, 8080);
        socket.send(packet); // 发送数据报
    } catch (IOException e) {
        e.printStackTrace();
    }
}

UDP数据接收端:

// UDP接收端
public static void main(String[] args) {
    int port = 8080; // 接收端口

    try(DatagramSocket socket = new DatagramSocket(port);){
        byte[] data = new byte[1024]; // 用于存放接收到的数据
        DatagramPacket packet = new DatagramPacket(data, 0, data.length);
        socket.receive(packet); // 接收数据报
        // 输出接收到的数据,packet.getData()是接收到数据的字节数组,packet.getLength()数据的长度
        System.out.println(new String(packet.getData(),0,packet.getLength()));
    }catch (Exception e){
        e.printStackTrace();
    }
}

在UDP中不存在连接,发送端发送只管发送数据不管接收端能否接收到数据,如果接收端没有开启接收数据会将发送的数据全部丢弃。

3.4、UDP实现在线聊天

实现在线聊天需要使用多线程,一个发送消息的线程,一个接收消息的线程,这两个线程都不能结束,只有发送接收到bye数据的时候才会自动结束线程。

User类:用于充当用户来发送和接收消息,这类可以序列化。

public class User implements Serializable {
    public static final long serialVersionUID = 21783618242834L;

    private Long id;
    private String name;
    private Integer age;
}

发送线程:

public class SendThread implements Runnable{

    private final User user; // 发送者信息
    private final int toPort; // 接收者的端口号

    public SendThread(User user, int toPort) {
        this.user = user;
        this.toPort = toPort;
    }

    @Override
    public void run() {

        try(DatagramSocket socket = new DatagramSocket();
            Scanner in = new Scanner(System.in);

           ){
            while (true){
                // 发送线程输入发送的数据
                String message = in.nextLine();

                // 将发送者和发送的消息封装到一个map中
                HashMap<String, Object> data = new HashMap<>();
                data.put("user",this.user);
                data.put("data",message);

                // 使用对象流将map数据转换成一个字节数组
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
                objectOutputStream.writeObject(data);
                byte[] array = outputStream.toByteArray();

                // 接收者的IP地址
                InetAddress address = InetAddress.getByName("localhost");

                // 发送数据报
                DatagramPacket packet = new DatagramPacket(array, 0, array.length, address, toPort);
                socket.send(packet); // 发送数据

                // 当发送的数据是bye时,关闭发送线程
                if ("bye".equalsIgnoreCase(message)){
                    break;
                }
                outputStream.close();
                objectOutputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

接收线程:

public class ReceiveThread implements Runnable{

    private final int port; // 接收端的端口号

    public ReceiveThread(int port) {
        this.port = port;
    }

    @Override
    public void run() {
        try(DatagramSocket socket = new DatagramSocket(port);
           ){
            while (true){
                // 而接收数据报
                byte[] buffer = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
                socket.receive(packet);

                // 使用对象流将数据报中的数据转换成一个map对象
                ByteArrayInputStream inputStream = new ByteArrayInputStream(packet.getData(),0,packet.getLength());
                ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
                Map<String,Object> receiveData = (Map<String, Object>) objectInputStream.readObject();
                User user = (User) receiveData.get("user");
                String message = (String) receiveData.get("data");
                System.out.println(user.getName() + ":"+message);

                // 如果接收到的数据为bye时,自动关闭接收数据线程
                if ("bye".equalsIgnoreCase(message)){
                    break;
                }
                inputStream.close();
                objectInputStream.close();
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

user1:作为一个测试用户。

public static void main(String[] args) {
    User user = new User(1001L, "张三", 19);
    new Thread(new SendThread(user,8080)).start(); // 开启发送线程发送给localhost的8080端口
    new Thread(new ReceiveThread(8081)).start(); // 开启接收端口,接收端口为8081
}

user2:作为测试用户。

public static void main(String[] args) {
    User user = new User(1002L, "李四", 20);
    new Thread(new SendThread(user,8081)).start(); // 开启发送线程,发送给localhost的8081端口
    new Thread(new ReceiveThread(8080)).start(); // 开启8080的接收端口
}

测试截图:

image-20220528161536260

image-20220528161545608

4、URL编程

URL:统一资源定位符,它表示网络上某一资源的地址。URL一般由 <传输协议>://<IP地址>:<端口号>/<文件名>?<参数名=参数值>这几个部分组成。

URL url = new URL("https://www.baidu.com:80/index.jsp?userName=admin");
System.out.println(url.getProtocol()); // 协议
System.out.println(url.getPort()); // 端口号
System.out.println(url.getHost()); // 主机ip
System.out.println(url.getPath()); // 文件路径
System.out.println(url.getFile()); // url全路径,包括参数
System.out.println(url.getQuery()); // 参数

下载网络资源:找到一个网络上资源的地址。

// 下载网络资源
public static void main(String[] args) {
    // 文件的url地址
    String urlString = "https://vd4.bdstatic.com/mda-impkfp8xqfzextht/sc/mda-impkfp8xqfzextht.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1653730175-0-0-41c75d1448282838e0fffd3f213acfda&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=3574894617&vid=15024048801031022508&abtest=101830_1-102148_2-17451_1&klogid=35748946171342423";
    try{
        URL url = new URL(urlString);
        // 打开网络连接
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 
        urlConnection.connect();

        // 使用缓冲流来读取和写入数据
        InputStream inputStream = urlConnection.getInputStream();
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        FileOutputStream fileOutputStream = new FileOutputStream(System.currentTimeMillis() + ".mp4");
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

        byte[] data = new byte[1024];
        int len = -1;
        while ((len = bufferedInputStream.read(data)) != -1){ // 读取数据
            bufferedOutputStream.write(data,0,len); // 写入数据
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值