考研二战失败 我的从零开始Java入门学习(十二)

文章目录

  • 一、File类
    • 1.1 构造方法
    • 1.2 常用类方法
    • 1.3 实际操作
  • 二、IO流
    • 2.1 字节流
      • 2.1.1 字节输出流
      • 2.1.2 字节输入流
      • 2.1.3 复制文件
    • 2.2 字符流
      • 2.2.1 字符输入流
      • 2.2.2 字符输入流
      • 2.2.3 复制文件
    • 2.3 缓冲流
    • 2.3.1 字节缓冲流
    • 2.3.2 字符缓冲流
    • 2.4 序列化流
      • 2.4.1 ObjectOutputStream类
      • 2.4.2 ObjectInputStream类


一、File类

`Java文件类以抽象的方式代表文件名和目录路径名。该类主要用于文件和目录的创建、文件的查找和文件的删除等。

File对象代表磁盘中实际存在的文件和目录。通过以下构造方法创建一个File对象。

文件和目录是 可以通过File封装成对象的。

对于File而言, 其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的。

1.1 构造方法

File(File parent, String child):通过给定的父抽象路径名和子路径名字符串创建一个新的File实例

File(String pathname) :通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例

File(String parent, String child):根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例

File(URI uri):通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。

1.2 常用类方法

创建功能:

public boolean mkdir()//make directory
创建此抽象路径名指定的目录

public boolean mkdirs()
创建此抽象路径名指定的目录,包括创建必需但不存在的父目录

public boolean createNewFile() throws IOException
当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件。

public static File createTempFile(String prefix, String suffix, File directory) throws IOException:
在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称

public static File createTempFile(String prefix, String suffix) throws IOException
在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称

判断和获取功能:

public boolean isDirectory()
测试此抽象路径名表示的文件是否是一个目录

public boolean isFile()
测试此抽象路径名表示的文件是否是一个标准文件

public boolean exists()
测试此抽象路径名表示的文件或目录是否存在

public String getAbsolutePath()
返回抽象路径名的绝对路径名字符串

public String getPath()
将此抽象路径名转换为一个路径名字符串

public String getName()
返回由此抽象路径名表示的文件或目录的名称

public String[] list()
返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组

public File[] listFiles(FileFilter filter)
返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器

public File[] listFiles()
返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件。

public File[] listFiles(FileFilter filter)
返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。

删除功能:

public boolean delete()
删除此抽象路径名表示的文件或目录

1.3 实际操作

        //构造File类
        File f1 = new File("E:\\Test.txt");
        //路径被视为字符串传入,在字符串中斜杠“\”被当做转义字符识别(如\t),所以要用“\\”才可表示一个斜杠

        System.out.println(f1.createNewFile());
        //创建新文件 若文件不存在则创建返回true,若文件已存在返回false

        File f2 = new File("E:\\Test");
        f2.mkdir();
        //创建新目录(仅创建单目录)

        File f3 = new File("E:\\Test1\\TestWord");
        f2.mkdirs();
        //创建新目录(可创建多级不存在目录)

        File f4 =new File("src\\test.text");
        f4.createNewFile();
        //根据相对路径在项目中创建文件

        f4.delete();
        //删除目录/文件

输出指定目录所有目录及文件:

  public static void main(String[] args) throws IOException {
        File f1 = new File("E:\\binary");
        GetAllPath(f1);
        }

  public static void GetAllPath(File src){
          File[]  filesArray = src.listFiles();
          if(filesArray != null){
              for(File file : filesArray){
                  if(file.isDirectory()){
                      System.out.print(file.getName()+"-");
                      GetAllPath(file);
                  }
                  else System.out.println(file.getName());
              }
          }
          else System.out.println();
    }

二、IO流

参考资料:
https://www.cnblogs.com/yichunguo/p/11775270.html#字符流reader和writer的故事(力推 写的很好)
https://www.cnblogs.com/yichunguo/p/11775270.html

Input/Output:输入输出机制

输入机制:允许java程序获取外部设备的数据(磁盘,光盘,网络等)。

输出机制:保留java程序中的数据,输出到外部设备上(磁盘,光盘等)。

IO流常用于处理设备间的数据传输问题,例如:文件复制、文件上传、文件下载。

IO流通过不同的方法,可对其进行分类。

1.按照功能来划分:
输入流:只能从中读取数据,而不能向其写入数据。
输出流:只能向其写入数据,而不能从中读取数据。

2.按照处理单元来划分
字节流和字符流操作的方式基本上完全相同。操作的数据单元不同
字节流:操作的是8位的字节(Byte)InputStream/OutputStream 作为字节流的基类
字符流:操作的是16位的字符(Character)Reader/Writer 作为字符流的基类

3.按照角色进行划分
节点流:可以直接从/向外部设备读取/写入数据的流,称之为节点流,节点流也被称之为低级流。
处理流:对于已经存在的流进行了连接和封装,扩展了原来的读/写的功能。处理流也被称之为高级流。

java的io包当中包括40多个流,他们都有紧密的联系,和使用的规律,这些流都源于4个抽象基类。

InputStream / Reader :所有的输入流的基类,前者是字节输入流,后者是字符输入流。

OutputStream/Writer :所有输出流的基本,前者是字节输出流,后者是字符输出流。

考研二战失败 我的从零开始Java入门学习(十二)_第1张图片
以下所有类的构造方法均有两种:
1、(File file):创建一个新的类 ,给定要读取的File对象。
2、 (String fileName):创建一个新的类,给定要读取的文件的字符串名称。

2.1 字节流

2.1.1 字节输出流

java.io.OutputStream抽象类是表示字节输出流的所有类的超类(父类),将指定的字节信息写出到目的地。

OutputStream类共有方法:

public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b):将 b.length个字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 也就是说从off个字节数开始读取一直到len个字节结束
public abstract void write(int b) :将指定的字节输出流 只能输入数字然后根据ASCII码转换

而接下来着重讲的是OutputStream类的子类:FileOutputStream类。

FileOutputStream类(文件输出流)

        FileOutputStream fos = new FileOutputStream("fos.txt");  //构造方法
        byte [] bt = "abcde".getBytes(); //abcde
        fos.write(97); //write() 根据ASCII码输出A
        fos.write(bt);
        fos.close();

需要注意的是,创建输出流对象的时候,系统会自动去对应位置创建对应文件,而创建输出流对象的时候,文件不存在则会报FileNotFoundException异常,也就是系统找不到指定的文件异常。

若要求FileOutputStream实现数据追加续写,需要更改构造方法:

1、public FileOutputStream(File file, boolean append)

2、public FileOutputStream(String name, boolean append)

这两个构造方法,第二个参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示不追加也就是清空原有数据。

换行需要回车符\r和换行符\n :
回车符:回到一行的开头(return)。
换行符:下一行(newline)。

Windows系统里,每行结尾是 回车+换行 ,即\r\n;
Unix系统里,每行结尾只有 换行 ,即\n;
Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。

故上述代码可更改如下:

        FileOutputStream fos = new FileOutputStream("fos.txt",true);  //构造方法(追加)
        byte [] bt = "abcde".getBytes(); //abcde
        fos.write(97); //write() 根据ASCII码输出A
        fos.write(bt);
        fos.write("\r\n".getBytes());  //换行
        fos.write(bt,2,2); //从索引2开始写入2字节即cd
        fos.close();

2.1.2 字节输入流

java.io.InputStream 抽象类是表示字节输入流的所有类的超类(父类),可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

InputStream类共有方法:

public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read(): 从输入流读取数据的下一个字节。
public int read(byte[] b): 该方法返回的int值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1

FileInputStream类(文件输入流)

1.一次读取一字节数据:

        FileInputStream fis = new FileInputStream("fos.txt");

        // 读取数据,返回一个字节
        int read = fis.read();  //读取的是int
        System.out.println((char) read); //需要转换

        //读取第二个数据 反复读取
        read = fis.read();  //读取的是int
        System.out.println((char) read); //需要转换
        
        fis.close();

反复读取直至末尾后,会返回值为-1int型数据,故上述代码可改为:

        FileInputStream fis = new FileInputStream("fos.txt");
        int read;
        while ((read = fis.read())!=-1) {
            System.out.println((char)read);
        }
        fis.close();

2.一次读取一字节数组数据:

        FileInputStream fis = new FileInputStream("fos.txt");

        int len;
        // 定义字节数组,作为装字节数据的容器
        byte [] bt = new byte[5];

        while (( len = fis.read(bt))!=-1) {
            // 每次读取后,把数组变成字符串打印
            System.out.println(new String(bt,0,len));//len为每次读取的有效字节个数 若不设置则在末尾可能会有旧数据
        }

        fis.close();

需要注意的是,在读取数据的过程中,会将换行符和回车符读取。

例如有文件中字符为:
hello
world
实际上输出的结果是:
hello

wor
ld
原因在于字符的具体内容为:
hello\r\n
world\r\n

2.1.3 复制文件

复制文本文件包括文本文件、图片、视频等,方式一样,需要创建流对象(指定数据源,指定目的地),然后读写数据,最后释放资源即可,原因在于所有文件实际上都是由字符组成。

        FileInputStream fis = new FileInputStream("Test.jpg");
        FileOutputStream fos = new FileOutputStream("Test_copy.jpg");

        byte[] bt = new byte[1024];

        int len;

        while ((len = fis.read(bt))!=-1) {

            fos.write(bt, 0 , len);
        }
        fos.close();
        fis.close();

2.2 字符流

考研二战失败 我的从零开始Java入门学习(十二)_第2张图片

因为数据编码的不同,因而有了对字符进行高效操作的流对象,字符流本质其实就是基于字节流读取时,去查了指定的码表,而字节流直接读取数据会有乱码的问题(读中文会乱码)。原因是因为中文的存储实际上是占用多字节的(用GBK编码占用2字节,用UTF-8编码占用3字节)。字符流实际上=字节流+编码表。

2.2.1 字符输入流

java.io.Writer 抽象类是字符输出流的所有类的超类(父类),将指定的字符信息写出到目的地。它同样定义了字符输出流的基本共性功能方法。

Writer类共有方法:

void write(int c) 写入单个字符
void write(char[] cbuf) 写入字符数组
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
void write(String str) 写入字符串
void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
void flush() 刷新该流的缓冲
void close() 关闭此流,但要先刷新它

需要注意的是,调用write方法进行操作后,数据只是保存到缓冲区,并未直接保存到文件。需要通过flush()刷新或者close()(先刷新缓冲区,然后通知系统释放资源)将数据从缓冲区存储到文件。

OutputStreamReader类(推荐)

        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("fos.txt"));

        osw.flush();
        osw.write("测试一哈");
        osw.flush();

        osw.close();

FileWriter类

        FileWriter fw = new FileWriter("fos.txt");

        fw.flush();
        fw.write("测试一哈");
        fw.flush();

        fw.close();

追加和续写:

        FileWriter fw = new FileWriter("fos.txt"true); //续写     
      	// 写出字符串
        fw.write("测试");
      	// 写出换行
      	fw.write("\r\n");
      	// 写出字符串
  		fw.write("一哈");

二者区别同下。

2.2.2 字符输入流

java.io.Reader抽象类是字符输入流的所有类的超类(父类),可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

Reader类共有方法:

public void close():关闭此流并释放与此流相关联的任何系统资源。
public int read():从输入流读取一个字符。
public int read(char[] cbuf):从输入流中读取一些字符,并将它们存储到字符数组cbuf中

close :先刷新缓冲区,然后通知系统释放资源

        FileReader fr = new FileReader("fos.txt");
        char [] bt = new char[1024];
        int len;
        // 每次读取后,把数组变成字符串打印
        while((len = fr.read(bt))!= -1) System.out.print(new String(bt, 0, len));

        fr.close();

和字节输入流类似,区别在于需要使用char数组来进行记录。

InputStreamReader类(推荐)

        InputStreamReader fr = new InputStreamReader(new FileInputStream("fos.txt"));
        char [] bt = new char[1024];
        int len;
        // 每次读取后,把数组变成字符串打印
        while((len = fr.read(bt))!= -1) System.out.print(new String(bt, 0, len));

        fr.close();

二者区别:

InputStreamReader是输入字节到字符的转换流,所以其构造方法的参数必须是InputStream类型的,并且可以在构造方法中重新指定编码的方式; InputStreamReader是FileReader的父类。

FileReader是便捷类,构造器的参数可以是文件的路径字符串,也可以是File类;不需要new 一个InputStream作为参数。

InputStreamReader是用来读取原始字节流,可指定编码格式,而FileReader是读取字符流,使用系统默认的编码格式,当读取中文文件易出现乱码问题。

2.2.3 复制文件

        //创建输入流对象
        FileReader fr=new FileReader("C:\\Users\\林凯\\Desktop\\Test.txt");//文件不存在会抛出java.io.FileNotFoundException
        //创建输出流对象
        FileWriter fw=new FileWriter("C:\\Users\\林凯\\Desktop\\TestCopy.txt");

        char chs[]=new char[1024];
        int len=0;
        while((len=fr.read(chs))!=-1) {//读数据
            fw.write(chs,0,len);//写数据
        }
        fw.flush();

        fr.close();
        fw.close();

这里需要注意的是,字符流只能操作文本文件,不能操作图片、视频等非文本文件。

因为字符流=字节流+编码集。字符流能把字节转换成文字因为有编码集指导其转换。而字符流可以读取汉字,只是直接创建字符流会假定你读取的文件使用了正确的编码,不然就自己包装字符流。例如在读的时候会将字节转换为字符,在转换过程中,可能找不到对应的字符,就会用?代替。这样在写的过程中不能有效还原字节,因此不能操作图片、视频等非文本文件。

因此单纯读或者写文本文件时使用字符流,其他情况使用字节流。

2.3 缓冲流

首先讲述一下缓冲流是什么

在读写数据时,让数据在缓冲区能够减少系统实际对原始数据来源的存取次数,因为一次能做多个数据单位的操作。相较而言,对于直接从文件读取数据或将数据写入文件,比起缓冲区的读写要慢多了。所以使用缓冲区的流,一般都会比没有缓冲区的流效率更高。拥有缓冲区的流被称为缓冲流,包括BufferedInputStream、 BufferedOutputStream类和BufferedReader、BufferedWriter类。缓冲流把数据从原始流成块读入或把数据积累到一个大数据块后再成批写出,通过减少资源的读写次数来加快程序的执行。

缓冲流的基本原理:

1、使用了底层流对象从具体设备上获取数据,并将数据存储到缓冲区的数组内。
2、通过缓冲区的read()方法从缓冲区获取具体的字符数据,这样就提高了效率。
3、如果用read方法读取字符数据,并存储到另一个容器中,直到读取到了换行符时,将另一个容器临时存储的数据转成字符串返回,就形成了readLine()功能。

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

字节缓冲流: BufferedInputStream,BufferedOutputStream
字符缓冲流: BufferedReader,BufferedWriter

2.3.1 字节缓冲流

public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流,注意参数类型为InputStream。该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用。

public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流,注意参数类型为OutputStream。创建BufferedlnputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次可以有很多字节。

需要注意的是字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作。

        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("fos.txt"));
        bos.write("Hello\r\n".getBytes());
        bos.write("World\r\n".getBytes());
        bos.close();

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("fos.txt"));
        int len;
        // 定义字节数组,作为装字节数据的容器
        byte [] bt = new byte[1024];
        while ((len = bis.read(bt))!= -1) {
            // 每次读取后,把数组变成字符串打印
            System.out.print(new String(bt,0,len));//len为每次读取的有效字节个数 若不设置则在末尾可能会有旧数据
        }
        bis.close();

代码与上述的字节输入流、字节输出流类似,但缓冲流最大的优势在于:快!

我们用字节缓冲流复制视频来比较一下:

      	// 记录开始时间
        long start = System.currentTimeMillis();
		// 创建流对象
        try (
		 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("py.exe"));
		 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copyPy.exe"));
        ){
          	// 读写数据
            int len;
            byte[] bytes = new byte[8*1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0 , len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");

和上述方法一同复制文件,用缓冲流要快的多。

2.3.2 字符缓冲流

public BufferedReader(Reader in) :创建一个新的缓冲输入流,注意参数类型为Reader。从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途。

public BufferedWriter(Writer out): 创建一个新的缓冲输出流,注意参数类型为Writer。将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小默认值足够大,可用于大多数用途。

而字符缓冲流有两个特有的方法:

BufferedReaderpublic String readLine(): 读一行数据。 读取到最后返回null
BufferedWriterpublic void newLine(): 换行,由系统属性定义符号

        // 创建流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("Test.txt"));
        // 写出数据
        bw.write("爱在~");
        // 写出换行
        bw.newLine();
        bw.write("西元前~");
        bw.newLine();
        // 释放资源
        bw.close();
        // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("Test.txt"));
        // 定义字符串,保存读取的一行文字
        String line  = null;
        // 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {
            System.out.println(line);
        }
        // 释放资源
        br.close();

2.4 序列化流

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:
考研二战失败 我的从零开始Java入门学习(十二)_第3张图片

2.4.1 ObjectOutputStream类

FileOutputStream fileOut = new FileOutputStream("Test.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

一个对象要想序列化,必须满足两个条件:
该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。

该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient关键字修饰。

public class Student implements java.io.Serializable{
    private String name;
    private int age;
    private transient String address;  //不序列化
    
    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 String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    Student(){

    }

public final void writeObject (Object obj) : 将指定的对象写出。

        Student s = new Student();
        s.setName("周杰伦");
        s.setAge(20);
        s.setAddress("中国台湾");
        try{
            FileOutputStream fos = new FileOutputStream("Test.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);  //转换流
            oos.writeObject(s);   //写入文件
            fos.close();
            oos.close();
            System.out.println("Serialized data is saved");
        }
        catch (IOException e){
            e.printStackTrace();
        }
输出结果:
Serialized data is saved

文件内容:
在这里插入图片描述
变成了一个只有反序列化流能读懂的文件。

2.4.2 ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

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

        Student newS = null;
        try {
            // 创建反序列化流
            FileInputStream fileIn = new FileInputStream("Test.txt");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            // 读取一个对象
            newS = (Student) in.readObject();
            // 释放资源
            in.close();
            fileIn.close();
        }catch(IOException e) {
            // 捕获其他异常
            e.printStackTrace();
            return;
        }catch(ClassNotFoundException c)  {
            // 捕获类找不到异常
            System.out.println("Student class not found");
            c.printStackTrace();
            return;
        }
        // 无异常,直接打印输出
        System.out.println("Name: " + newS.getName());
        System.out.println("Address: " + newS.getAddress());
        System.out.println("age: " + newS.getAge());

/*输出结果:
Name: 周杰伦
Address: null transient了
age: 20*/


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

反序列化操作2
另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

1、该类的序列版本号与从流中读取的类描述符的版本号不匹配
2、该类包含未知数据类型
2、该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

public class Student implements java.io.Serializable {
     // 加入序列版本号
     private static final long serialVersionUID = 1L;
     public String name;
     public int age;
     public transient String address;
     // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
     public int eid; 

     public void addressCheck() {
         System.out.println("Address  check : " + name + " -- " + address);
     }
}

你可能感兴趣的:(java,学习,开发语言)