文件操作和IO详解

文件操作和IO详解_第1张图片

文件操作 和 IO

文件,File 这个概念,在计算机里,也是“一词多用”.

文件的狭义和广义

狭义的文件: 指的是硬盘上的文件和目录(文件夹)
广义的文件: 泛指计算机中很多的软硬件资源.操作系统中,把很多的硬件设备和软件设备都抽象成了文件.按照文件的方式来统一管理.例如网卡,操作系统就是把网卡当成了一个文件.
注: 平时谈到的“文件”,指的都是狭义的文件,也就是硬盘上的文件.

硬盘(外存)和内存相比

速度: 内存比硬盘快很多.
空间: 内存空间比硬盘小.
成本: 内存比硬盘快.
持久化: 内存掉电后数据丢失,外存掉电后数据还在.

文件路径

每个文件,在硬盘上都有一个具体的“路径”.

\ => 反斜杠 (使用 \ ,写代码的时候很不方便)
/ => 斜杠 (建议大家优先使用)

表示一个文件的具体位置路径是,就可以使用 / 来分割不同的目录级别.

文件操作和IO详解_第2张图片

文件操作和IO详解_第3张图片

在路径这里,有两种表示路径的风格.

  1. 绝对路径.以c: d:盘符开头的路径.

  2. 相对路径.以当前所在的目录为基准,以 . 或者 … 开头(. 有时候可以省略),找到指定的路径.

    当前所在的目录称为工作目录.每个程序运行的时候,都有一个工作目录(在控制台里通过命令操作的时候,是特别明显的,后来进化到图形化界面了,工作目录就不那么直观了).

文件操作和IO详解_第4张图片

工作目录不同,定位到同一个文件,相对路径写法是不同的.

同样是定位到 java_code 这里
如果工作目录是 d:/ ,相对路径写作 ./code/java_code
如果工作目录是 d:/code ,相对路径写作 ./java_code
如果工作目录是 d:/code/python_code ,相对路径写作 …/java_code (… 表示当前目录的上级目录)
如果工作目录是 d:/code/python_code/2023 ,相对路径写作 …/…/java_code
注: 我们日常写java代码一般都会使用IDEA,当我们打开IDEA之后,IDEA的默认工作路径就是你的当前项目的所在目录. 如果代码中写了一些相对路径的代码,工作路径就是以上述路径为基准的!!!

在 Windows 电脑,命令行下,直接输入某个程序的名字,本质上是操作系统去PATH环境变量里查找的. calc (Windows自带的计算器) 本身就在 PATH 下,所以可以直接运行.自己装的程序,比如 qq 之类的,默认不行.但是如果把 qq.exe 的路径也加入到 PATH 就可以了.
注: Linux没有盘符的概念,统一是使用 cd 切换. Windows 有盘符,先定位盘符,再 cd 在当前盘符下切换.

文件的类型

文件分类: 文本 vs 二进制
word,exe,图片,视频,音频,源代码,动态库……这些不同的文件,整体可以归纳到两类中,文本文件二进制文件.

文本文件

文本文件: 存的是文本,字符串.
字符串,是由字符构成的,每个字符,都是通过一个数字来表示的.
这个文本文件里存的数据,—定是合法的字符,都是在你指定字符编码的码表之内的数据.

二进制文件

二进制文件: 存的是二进制数据,不一定是字符串了,没有任何限制,可以存储任何你想要你想要的数据.

随便给你个文件,如何区分文本还是二进制?
直接使用记事本打开.如果乱码了,说明就是二进制.如果没乱说明就是文本.

Java 对于文件的操作

  1. 针对文件系统操作(创建文件,删除文件,重命)
  2. 针对文件内容操作(文件的读和写)

File 类

Java 标准库,提供了一个 File 这个类(java.io.File), 这个 FIle 类,封装了文件系统操作.
注: 有 File 对象,并不代表真实存在该文件.

属性

修饰符及类型 属性 说明
static String pathSeparator 依赖于系统的路径分隔符,String 类型的表示
static char pathSeparator 依赖于系统的路径分隔符,char 类型的表示

注: pathSeparator => File 里的一个静态变量, 它是 / 还是 \ , 取决于操作系统.

构造方法

签名 说明
File(File parent, String child) 根据父目录 + 孩子文件路径,创建一个新的 File 实例
File(String pathname) 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者 相对路径
File(String parent, String child) 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用 路径表示

在 new File 对象的时候,构造方法参数中,可以指定一个路径,此时 File 对象就代表这个路径对应的文件了(指定一个路径 => 这里绝对路径和相对路径都行).
parent 表示当前文件所在的目录, child 自身的文件名.例如: d:/fly.jpg => parent d:/ , child fly.jpg

方法

**方法 **~~ 不需要死记,了解有印象即可

修饰符及返回值类型 方法签名 说明
String getParent() 返回 File 对象的父目录文件路径
String getName() 返回 File 对象的纯文件名称
String getPath() 返回 File 对象的文件路径
String getAbsolutePath() 返回 File 对象的绝对路径
String getCanonicalPath() 返回 File 对象的修饰过的绝对路径
boolean exists() 判断 File 对象描述的文件是否真实存在
boolean isDirectory() 判断 File 对象代表的文件是否是一个目录
boolean isFile() 判断 File 对象代表的文件是否是一个普通文件
boolean createNewFile() 根据 File 对象,自动创建一个空文件, 成功创建后返 回 true
boolean delete() 根据 File 对象,删除该文件, 成功删除后返回 true
void deleteOnExit() 根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行
String[] list() 返回 File 对象代表的目录下的所有文件名
File[] listFiles() 返回 File 对象代表的目录下的所有文件,以 File 对象 表示
boolean mkdir() 创建 File 对象代表的目录
boolean mkdirs() 创建 File 对象代表的目录,如果必要,会创建中间目录
boolean renameTo(File dest) 进行文件改名,也可以视为我们平时的剪切、粘贴操 作
boolean canRead() 判断用户是否对文件有可读权限
boolean canWrite() 判断用户是否对文件有可写权限

代码示例1 ~~ 文件路径

import java.io.File;
import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-13
 * Time: 20:21
 */
public class IODemo1 {
    public static void main(String[] args) throws IOException {
        File file = new File("d:/test.txt");// 不要求在 d:/ 这里真的有个 test.txt
        /*
        * 注: 这个 test.txt文件不存在的话,是不会自动在 D盘下创建一个 test.txt
        * 不过可以使用 createNewFile 手动创建.
        * */
//      File file=new File("./test.txt"); 第二类写法,使用相对路径,当前目录是项目所在路径
        System.out.println(file.getName());// 获取到文件名
        System.out.println(file.getParent());// 获取父级路径
        System.out.println(file.getPath());// 获取到完整路径
        System.out.println(file.getAbsolutePath());// 获取绝对路径
        System.out.println(file.getCanonicalFile());// 获取绝对路径的简化路径
    }
}

运行结果
第一类写法的运行结果
文件操作和IO详解_第5张图片

第二类写法的运行结果
文件操作和IO详解_第6张图片

代码示例2 ~~ 文件创建

import java.io.File;
import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-13
 * Time: 21:19
 */
public class IODemo2 {
    public static void main(String[] args) throws IOException {
//      File file = new File("d:/test.txt");
        File file = new File("./test.txt");
        file.createNewFile();// 创建文件
        System.out.println(file.exists());// 判断是否存在
        System.out.println(file.isFile());// 判断是否是文件
        System.out.println(file.isDirectory());// 判断是否是目录
    }
}

运行结果

文件操作和IO详解_第7张图片

代码示例3 ~~ 文件删除

import java.io.File;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-13
 * Time: 21:28
 */
public class IODemo3 {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        file.delete();// 文件删除
    }
}

deleteOnExit 程序退出的时候,自动删除.程序中需要使用到一些"临时文件”的时候,需要用到.

文件操作和IO详解_第8张图片

代码示例4 ~~ 创建目录

import java.io.File;

/**
 * Created with IntelliJ IDEA.
 * Description: 目录创建
 * User: fly(逐梦者)
 * Date: 2023-10-13
 * Time: 21:56
 */
public class IODemo4 {
    public static void main(String[] args) {
        File dir=new File("./test");
//      File dir=new File("./test/aaa/bbb");
        // mkdir 只能创建一直目录
        // mkdirs 才能创建多级目录
        // mk => make (制造,创建), dir => directory (目录)
        dir.mkdirs();
    }
}

运行结果

文件操作和IO详解_第9张图片

代码示例5 ~~ 文件重命名

import java.io.File;

/**
 * Created with IntelliJ IDEA.
 * Description: 重命名
 * User: fly(逐梦者)
 * Date: 2023-10-13
 * Time: 22:36
 */
public class IODemo5 {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        File dest = new File("./testAAA");
        file.renameTo(dest);
        file.delete();
    }
}

运行结果

文件操作和IO详解_第10张图片

文件内容的操作

针对文件内容,使用**“流对象”**进行操作.

流对象 => 形象的比喻
计算机里的很多概念,都使用了一定的修辞手法.比喻是一种常见的方式.
使用一个链表头结点/二叉树根节点,表示整个链表/二叉树,这也是一个修辞手法借代也是一种常见的方式,用局部表示整体.
比如白居易的<<长恨歌>>:“六军不发无奈何,宛转蛾眉马前死.”
峨眉,古代女子纹的眉毛,用眉毛代替杨贵妃.

文件操作和IO详解_第11张图片

Java标准库的流对象.从类型上,分成两个大类:

  1. 字节流: 以字节为单位,读写数据(操作二进制数据)
    InputStream => FileInputStream
    OutputStream => FileOutputStream

  2. 字符流: 以字符为单位,读写数据(操作文本数据)
    Reader => FileReader
    Writer => FileWriter

注: 这里虽然涉及的类很多,但是规律性很强.
InputStream,OutputStream,Reader,Writer 都是抽象类,不能直接实例化.

抽象类和接口,有什么区别

抽象类和接口的区别 ~~ [经典面试题]

(1)接口比抽象类更抽象,但是抽象类的功能是比接口更多的.
(2)抽象类和普通的类,差别就不大.只不过抽象类不能new实例,带有抽象方法.抽象类,可以有普通方法,也可以有普通的属性.
(3)接口里面都是抽象方法&不能有普通的成员.

注: 抽象类,大部分的东西都是确定.有几个属性,有几个方法,大部分都是明确的,只要一小部分是抽象方法
接口,大部分都是不确定.有什么属性,也不知道,方法也都是抽象方法(不考虑default的情况)
因此接口提供的信息量更少,视为接口比抽象类更抽象.

这些类的使用方式是非常固定的 ~~ 核心操作(四个)
(1)打开文件(构造对象)
(2)关闭文件(close)
(3)读文件(read) => 针对 InputSream/Reader
(4)写文件(write) => 针对``OutputStream/Writer`

字节流

InputStream 概述

方法

修饰符及返回值类型 方法签名 说明
int read() 读取一个字节的数据,返回 -1 代表已经完全读完了
int read(byte[] b) 最多读取 b.length 字节的数据到 b 中,返回实际读到的数 量;-1 代表以及读完了
int read(byte[] b, int off, int len) 最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返 回实际读到的数量;-1 代表以及读完了
void close() 关闭字节流

read无参数版本: 一次读一个字节.
read一个参数版本: 把读到的内容填充到参数的这个字节数组中.(此处的参数是个“输出型参数")―返回值是实际读取的字节数.
read三个参数版本: 和2类似,只不过是往数组的一部分区间里尽可能填充.

read()返回值为什么是int?
read读取的是一个字节,按理说,返回一个byte就行了,但是实际上返回是int.
除了要表示 byte 里的0 ->255 (-128->127)这样的情况之外,还需要表示一个特殊情况,-1这个情况表示读取文件结束了(读到文件末尾了)

代码示例 ~~ 读取文件

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-12
 * Time: 22:52
 */
public class IODemo6 {
    // 使用字节流来读取文件
    public static void main(String[] args) throws IOException {
        // 创建 InputStream 对象的时候,使用绝对路径或者相对路径,都是可以的,也可以使用 File 对象
        InputStream inputStream = new FileInputStream("d:/cat.jpg");

/*        // 进行读操作
        一次读取一个字节
        while (true){
           int b= inputStream.read();
            if (b==-1){
                // 读取完毕
                break;
            }
//          System.out.println(""+(byte)b);
            System.out.printf("%x\n",(byte)b);
        }*/

        // 一次读取若干个字节
        while (true) {
            byte[] buffer = new byte[1024];// read的第二个版本,需要调用者提前准备好一个数组.
            int len = inputStream.read(buffer);
            System.out.println("len: "+len);
            if (len == -1) {
                break;
            }
            // 此时读取的结果就被放到 byte 数组中.
/*            for (int i = 0; i < len; i++) {
                System.out.printf("%x\n",buffer[i]);
            }*/
        }
        inputStream.close();// 关闭文件
    }
}

文件操作和IO详解_第12张图片

buffer(缓存区)

buffer存在的意义,就是为了提高IО操作的效率.
单次IO操作,是要访问硬盘/IO设备,单次操作是比较消耗时间的,如果频繁进行这样的IO操作,耗时肯定就更多了.
单次IO时间是一定的,如果能缩短IO的次数,此时就可以提高程序整体的效率了.
第一个版本的代码,是一次读一个字节.循环次数就比较高. read次数也很高.
第二个版本的代码,是一次读1024个字节,循环次数就降低了很多.read次数变少了.
缓冲区,“缓和了一下冲突”,减少冲击的次数.

FileInputStream 概述

构造方法

签名 说明
FileInputStream(File file) 利用 File 构造文件输入流
FileInputStream(String name) 利用文件路径构造文件输入流

OutputStream 概述

方法

修饰符及返回值类型 方法签名 说明
void write(int b) 写入要给字节的数据
void write(byte[] b) 将 b 这个字符数组中的数据全部写入 os 中
int write(byte[] b, int off, int len) 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个
void close() 关闭字节流
void flush() 调用 flush(刷新)操作,将数据刷到设备中

注: OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream

代码示例 ~~ 写文件

使用了InputStream来读文件.还可以使用OutputStream来写文件.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-14
 * Time: 15:28
 */
public class IODemo7 {
    // 进行写文件
    public static void main(String[] args) throws IOException {

        OutputStream outputStream = new FileOutputStream("d:/test.txt");
        outputStream.write(97);
        outputStream.write(98);
        outputStream.write(99);
        outputStream.write(100);
        outputStream.close();
        
        /*  第二种写法
        try (OutputStream outputStream = new FileOutputStream("d:/test.txt")){
            outputStream.write(97);
            outputStream.write(98);
            outputStream.write(99);
            outputStream.write(100);
        }
        // 这个写法虽然没有显示的写close,实际上是会执行的.只要 try 语句块执行完毕,就可以执行到 close
        // 这个语法, 在 Java中被称为 try with resources
        // 注: 只有实现了 Closeable 接口的类才可以放到 try的() 中被自动关闭
        */
    }
}

文件操作和IO详解_第13张图片

outputStream.close;=> 这里的close操作,含义是,关闭文件.

进程 -> 在内核里,使用PCB这样的数据结构来表示进程.
一个线程对应一个PCB
一个进程可以对应一个PCB也可以对应多个…
PCB中有一个重要的属性,文件描述符表.(相当于一个数组)记录了该进程打开了哪些文件
(即使一个进程里有多个线程多个PCB,也没关系,这些PCB共用同一个文件描述符表

文件操作和IO详解_第14张图片


字符流

字符流用法和字节流基本差不多

示例代码 ~~ 字符读操作

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-14
 * Time: 19:32
 */
public class IODemo8 {
    // 字符流的操作
    public static void main(String[] args) {
        try (Reader reader = new FileReader("d:/test.txt")) {
            while (true) {
                int ch = reader.read();
                if (ch == -1) {
                    break;
                }
                System.out.print("" + (char) ch);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果
文件操作和IO详解_第15张图片

示例代码 ~~ 字符写操作

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-14
 * Time: 19:40
 */
public class IODemo9 {
    public static void main(String[] args) {
        try (Writer writer= new FileWriter("d:/test.txt")){
            writer.write("fly in the sky! -- from: 0213");
            // 手动刷新缓冲区
            writer.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果

文件操作和IO详解_第16张图片

writer.write("fly in the sky! -- from: 0213");
像这样的写操作,其实是先写到缓冲区里(缓冲区存在很多种形态.咱们自己的代码里可以有缓冲区;标准库里也可以有缓冲区∵操作系统内核里也可以有缓冲区…).
写操作执行完了,内容可能在缓冲区里,还没有真的进入硬盘.
close操作,就会触发缓冲区的刷新(刷新操作,就是把缓冲区里的内容写到硬盘里)
除了close之外,还可以通过 flush(推荐) 方法,也能起到刷新缓冲区的效果.

Scanner

Scanner 是搭配流对象进行使用的.

利用 Scanner 进行字符读取

构造方法 说明
Scanner(InputStream is, String charset) 使用 charset 字符集进行 is 的扫描读取

Scanner scanner = new Scanner(System.in); => System.in 其实就是一个输入流对象

示例代码

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: fly(逐梦者)
 * Date: 2023-10-14
 * Time: 19:50
 */
public class IODemo10 {
    public static void main(String[] args) {
        //Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream=new FileInputStream("d:/test.txt")){
            Scanner scanner = new Scanner(inputStream);
            // 此时读取的内容就是从 文件 进行读取了.
            scanner.next();
        }catch (IOException e){
            e.printStackTrace();
        }
        // 此时,内部的 inputStream 对象已经被 try() 关闭了.里面的这个 Scanner 不关闭,也没事
    }
}

文件操作和IO详解_第17张图片

小工具程序练习

练习一

扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件.

练习一题目

给定一个目录,目录里会包含很多的文件和子目录…
用户输入一个要查询的词,看看当前目录下(以及子目录里)是否有匹配的结果.如果有匹配结果,就进行删除.

代码实现

import java.io.File;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * // 给定一个目录,目录里会包含很多的文件和子目录
 * // 用户输入一个要查询的词,看看当前目录下(以及子目录里)是否有匹配的结果
 * // 如果有匹配结果,就进行删除.
 * User: fly(逐梦者)
 * Date: 2023-10-14
 * Time: 20:34
 */
public class IODemo11 {

    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        // 让用户输入一个指定搜索
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要搜索的路径: ");
        String basePath = scanner.next();

        // 针对用户输入进行简单判定
        File root = new File(basePath);
        if (!root.isDirectory()) {
            // 路径不存在,或者只是一个普通文件,此时无法进行搜索
            System.out.println("输入的目录有误!");
            return;
        }
        // 再让用户输入一个要删除的文件名
        System.out.println("请输入要删除的文件名:");
        // 此处要使用 next, 而不是使用 nextLine!!!
        String nameToDelete = scanner.next();
        // 针对指定的路径进行扫描 => 递归操作
        // 先从根目录出发.(root)
        // 先判定一下,当前这个目录里,看看是否包含我们要删除的文件.如果是,就删除;否则,就调过下一个.
        // 如果当前这里包含了一些目录,再针对子目录进行递归.

        scanDir(root, nameToDelete);
    }

    private static void scanDir(File root, String nameToDelete) {
        System.out.println("[scanDir]"+root.getAbsolutePath());
        // 1.先列出当前路径下包含的内容
        File[] files = root.listFiles();
        if (files == null) {
            // 当前 root 目录下没东西,是一个空目录
            // 结束继续递归
            return;
        }
        // 2.遍历当前的列出结果
        for (File f : files) {
            if (f.isDirectory()) {
                // 如果是目录,就进一步递归
                scanDir(f, nameToDelete);

            } else {
                // 如果是普通文件,则判定是否要删除
                if (f.getName().contains(nameToDelete)) {
                    System.out.println("确认是否要删除 " + f.getAbsolutePath() + " 嘛?");
                    String choice = scanner.next();
                    if (choice.equals("y")||choice.equals("Y")){
                        f.delete();
                        System.out.println("删除成功!");
                    }else {
                        System.out.println("删除成功!");
                    }
                }
            }
        }
    }
}

文件操作和IO详解_第18张图片

文件操作和IO详解_第19张图片

运行结果

文件操作和IO详解_第20张图片


练习二

进行普通文件的复制

练习二题目

把一个文件拷贝成另一个文件,就是把第一个文件按照字节依次读取,把结果写入到另一个文件中.

代码实现

import java.io.*;
import java.util.Date;
import java.util.Scanner;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * 把一个文件拷贝成另一个文件
 * 就是把第一个文件按照字节依次读取,把结果写入到另一个文件中
 * User: fly(逐梦者)
 * Date: 2023-10-14
 * Time: 22:16
 */
public class IODemo12 {
    public static void main(String[] args) {
        // 输入两个路径
        // 源 和 目标 (从哪里,拷贝到哪里)
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要拷贝那个文件: ");
        String srcPath =scanner.next();
        System.out.println("请输入要拷贝到那个地方: ");
        String destPath = scanner.next();
        File srcFile = new File(srcPath);
        if (!srcFile.isFile()){
            // 如果源不是一个文件(是个目录或者不存在)
            System.out.println("您当前输入的源路径有误!");
            return;
        }
        File destFile = new File(destPath);
        if (destFile.isFile()){
            // 如果已经存在,认为也不能拷贝
            System.out.println("您当前输入的目标路径有误!");
            return;
        }

        // 进行拷贝操作
        try (InputStream inputStream= new FileInputStream(srcFile);
             OutputStream outputStream=new FileOutputStream(destFile)){
            // try()语法,支持包含多个流对象,多个流对象之间使用 ; 分割开就行了

            // 进行读文件操作
            while (true){
                int b = inputStream.read();
                if (b==-1){
                    break;
                }
                outputStream.write(b);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

文件操作和IO详解_第21张图片

注: (1)由于这里的文件都是一个一个字节来进行读取的,所以无论哪种类型的文件都可以拷贝的.
(2)这里的代码只能进行文件的拷贝,如果需要拷贝目录的话,就需要按照上一个例子一样,递归的拷贝.

运行结果

文件操作和IO详解_第22张图片

你可能感兴趣的:(学习笔记,javaEE的学习,网络,java-ee,学习,学习方法,面试)