Java基础 Day17

今天的任务:
  1. Map遍历的3种方式能独立写出来,知道区别
  2. 掌握File工具类的API
  3. 掌握迭代遍历文件夹中所有内容的代码的写法,能独立写出来
    1. 输出目标文件夹中所有的内容
    2. 统计目标文件夹的大小
  4. 掌握I/O流的基本概念,能够独立写出读取文件内容并输出到控制台的代码

复习

集合
  • 保存批量数据(对象)的工具
  • Collection接口体系
    • List:有序可重复
      • 新添加的元素会放到集合的最末端
      • ArrayList
      • LinkedList
    • Set:无序不可重复
      • HashSet:底层基于HashMap保存数据,所添加的元素作为底层Map的key进行存储,所有的value都是同一个静态常量(Object对象)
        • hash表
      • TreeSet:底层基于TreeMap保存数据,所添加的元素作为底层Map的key进行存储,所有的value都是同一个静态常量(Object对象)
        • 排序二叉树-红黑树
  • Map接口体系
    • 保存的都是键值对形式的数据
    • 不允许出现重复的键,和一个键对多个值的映射
    • API:
      • map.put(key, value);
      • value = map.get(key);
    • 常用实现类
      • HashMap:
        • HashMap存储原理
          • 将一个键值对保存到HashMap中时,首先调用key的hashCode方法,获取它的hash值
          • 然后使用hash值和底层数组长度-1进行 & 运算,得到的结果作为该元素保存的下标
          • 判断底层数组下标位置是否有元素,如果没有,创建一个Node对象,封装key和value,然后保存到数组该位置
          • 如果数组目标位置有元素,使用要添加的key和该位置所有已存在的Node的key进行比较(equals方法)
          • 如果有相等的,则使用新的value覆盖该位置的value
          • 如果都不相等,创建一个Node对象,封装key和value,在数组该位置形成链表,新创建的Node对象放在链首,原有的对象向后移动
          • 如果某个链表上的节点个数超过了8个,会自动转变成红黑树结构(JDK1.8)
          • 如果一个对象要作为key保存到HashMap中,该对象的类必须重写hashCode和equals方法
        • HashMap的扩容机制
          • 底层数组的初始化长度为16,默认的加载因子是0.75,当保存的元素大于长度*加载因子,则触发扩容机制
          • 首先新建一个数组,长度是原数组的2倍
          • 遍历原数组中的所有Node,基于hash算法重新计算它们在新数组中的下标,然后将Node添加到新数组中
          • HashMap底层数组长度一定是2的幂
遍历Map集合
  • 先获取所有的键再获取所有的值

  • 直接获取所有的值并遍历

  • 先获取所有的键值对再获取键和值(优先)

  public class Test1 {
  
      public static void main(String[] args) {
          Student s1=new Student(1,"乔峰",35);
          Student s2=new Student(2,"段誉",18);
          Student s3=new Student(3,"虚竹",20);
          Student s4=new Student(4,"慕容复",32);
          Map<Integer,Student> map=new HashMap<>();
          map.put(s1.getId(),s1);
          map.put(s2.getId(),s2);
          map.put(s3.getId(),s3);
          map.put(s4.getId(),s4);
          // 遍历Map的方式一
          // map.keySet(); 返回所有key组成的set集合
          // Set<Integer> set1=map.keySet();
          // 需要遍历Map2次-第一次拿所有的key,第二次用key拿value
          for(Integer key : map.keySet()){
              System.out.println("key="+key+",value="+map.get(key));
          }
          // map.values(); 返回所有value组成的集合
          // 只有value,没有key
          for(Student s:map.values()){
              System.out.println("value="+s);
          }
          // map.entrySet(); 返回所有entry组成的集合 - 推荐用法
          // 这里遍历了一次Map,拿到了所有的键值对
          Set<Entry<Integer,Student>> set2=map.entrySet();
          // 这里的遍历,和Map没有关系了
          for(Entry<Integer,Student> entry:set2){
              System.out.println("key="+entry.getKey()+",value="+entry.getValue());
          }
      }
  
  }
  public class Test2 {
  
      public static void main(String[] args) {
          Student s1 = new Student(1,"乔峰",35);
          Map<Student,String> map = new HashMap<>();
          map.put(s1,s1.getName());
          System.out.println("name="+map.get(s1)); // "乔峰"
          s1.setAge(36);
          System.out.println("name="+map.get(s1)); // null-说明map中没有这个键值对
          // 原因:
          // 1. Student重写了hashCode方法,标准逻辑是基于所有属性的值计算一个int的结果
          // 2. 如果Student的任意属性值发生了变化,则hashCode值会发生变化
          // 3. 最初将student作为key保存时,是基于旧的hashCode计算的数组下标并存放
          // 4. 后面这次查询,是基于新的hashCode计算的数组下标并进行查找
          // 5. 两次映射的下标不同,在新位置找不到旧的键值对,所以结果为null
          // 如何规避这个问题:
          // 要求Map中的Key的类型最好使用 Java中的不可变类
      }
  }
  public class Test3 {
  
      public static void main(String[] args) {
          Student s1=new Student(1,"乔峰",35);
          Student s2=new Student(2,"段誉",18);
          Student s3=new Student(3,"虚竹",20);
          Student s4=new Student(4,"慕容复",32);
          // Map<Integer,Student> map=new HashMap<>(); // key是无序的
          Map<Integer,Student> map=new LinkedHashMap<>(); // key是有序的
          map.put(s3.getId(),s3);
          map.put(s1.getId(),s1);
          map.put(s4.getId(),s4);
          map.put(s2.getId(),s2);
          for(Map.Entry<Integer,Student> entry:map.entrySet()){
              System.out.println("key="+entry.getKey()+",value="+entry.getValue());
          }
      }
  }
  public class Test4 {
  
      public static void main(String[] args) {
          Student s1=new Student(1,"乔峰",35);
          Student s2=new Student(2,"段誉",18);
          Student s3=new Student(3,"虚竹",20);
          Student s4=new Student(4,"慕容复",32);
          Map<Integer,Student> map=new TreeMap<>(new Comparator<Integer>() {
              @Override
              public int compare(Integer o1, Integer o2) {
                  return o2-o1; // 自定义的比较逻辑
              }
          });
          // 按照Key进行排序
          map.put(s3.getAge(),s3);
          map.put(s1.getAge(),s1);
          map.put(s4.getAge(),s4);
          map.put(s2.getAge(),s2);
          for(Map.Entry<Integer,Student> entry:map.entrySet()){
              System.out.println("key="+entry.getKey()+",value="+entry.getValue());
          }
          // 需求:年龄从大到小排序
      }
  }
绝对路径和相对路径

绝对路径:从一个固定点到目标点的路径,在文件系统中,一般指从文件系统的根路径到目标文件/文件夹的路径

  • 例如:
    • windows:c:\user\admin\1.txt
    • linux:\user\admin\1.txt
  • 特点:
    • 不论你当前在哪个文件或文件夹下,目标文件的绝对路径都是一致的
    • 例如:从故宫到达内教育大厦的路径,与你当前所在的位置无关的

相对路径:从当前位置到目标点的路径,在文件系统中,一般指从当前文件夹到目标文件的路径

  • 例如:
    • windows:
      • 例1:
        • 当前文件夹:c:\user\
        • 目标文件夹:c:\user\admin\1.txt
        • 相对路径:admin\1.txt
      • 例2:
        • 当前文件夹:c:\user\admin
        • 目标文件夹:c:\1.txt
        • 相对路径:…/…/1.txt (…/表示返回上级目录,./表示的是当前目录)
  • 特点:
    • 如果当前所在位置发生了变化,则相对路径发生变化
    • 例如:我在达内教育大厦,搜索到故宫的路径,路径1。如果我现在在天坛,再搜索到故宫的路径,路径2,两个路径不同的。

问:绝对路径好还是相对路径好?

  • 绝对路径优点:准确,在任何地方都通用
  • 绝对路径的缺点:
    • 可能会很长很复杂
    • 如果大环境改变,则绝对路径可能会生效
  • 相对路径的优点:
    • 可以比较简短
    • 不受大环境改变的影响,只要2个文件的相对位置不变,则路径不受影响
  • 相对路径的缺点:
    • 如果2个文件的相对位置发生变化,则路径失效
    • 可能不适用于其他文件

File工具类

  • 可以使用File对象来封装一个文件或目录的信息,包括文件(或目录)名称、所在路径、文件长度等等。

  • File类提供了一些处理和获取文件信息的方法。

  • 还可基于File对象来打开一个文件进行读写操作。

  • 在Java技术中,目录也被看作是一种文件。因此,无论文件和目录,都可以使用File类来统一处理。

  • File对象可封装指定文件或目录的信息。例如:

    • 创建一个封装了名为"myfile.txt"文件的File对象:

      File myFile = new File(“myfile.txt”);

    • 创建一个封装了名为" MyDocs "目录的File对象:

      File myDir = new File(“MyDocs”);

  • 值得注意的是,File对象只封装文件或目录信息,它本身并不会打开文件甚至访问文件的内容。

  • 关于文件名称的方法:

    • String getName():返回文件(或目录)名称
    • String getPath():返回文件(或目录)的所在路径
    • String getAbsolutePath():返回文件(或目录)所在的绝对路径
    • String getParent():返回文件(或目录)所在的父目录
    • boolean renameTo(File newName):重命名文件(或目录)
  • 关于目录方法:

    • boolean mkdir():创建相应的目录
      • 该方法仅能够创建1级目录,如果路径中存在多级未创建的目录,则该方法返回false,创建失败
    • boolean mkdirs():一次性创建多级目录
    • String[] list():返回目录下的文件列表
    • File[] listFiles():返回目录下的文件列表,以File数组形式
  • 关于文件信息的方法:

    • long lastModified():返回文件(或目录)的最后修改时间
    • long length():返回文件(或目录)的长度
    • boolean delete():删除文件(或目录)
      • 不能直接删除非空的文件夹,需要先删除文件夹中所有内容,才能删除文件夹
  • 关于文件测试的方法:

    • boolean exists() :指示文件(或目录)是否存在
      • 当使用new File(“path”)创建File对象时,该path对应的路径下不一定存在目标文件或文件夹,这并不影响File对象的创建
      • 一个File对象既可以表示已经存在在硬盘上的文件或文件夹,也可以表示即将创建的文件或文件夹
    • boolean isFile() :指示该对象所封装的是否为文件
    • boolean isDirectory() :指示该对象所封装的是否为文件
public class TestFile {
    public static void main(String[] args) {
        File file = new File("/JavaSE/TestFile.java");

        System.out.println("Name = " + file.getName());
        System.out.println("Parent = " + file.getParent());
        System.out.println("Path = " + file.getPath());
        System.out.println("isDirectory = " + file.isDirectory());
        System.out.println("isFile = " + file.isFile());
        System.out.println("lastModified = " + file.lastModified());
        System.out.println("length = " + file.length());
        System.out.println("---------------------------");

        File dir = new File("test");
        dir.mkdir();

        File curDir = new File("./");
        String[] strs = curDir.list();
        for (String str : strs) {
            System.out.println(str);
        }
    }
}

I/O流

I/O流与文件操作

I/O 流的概念
  • I/O是Input/Output的缩写, I/O技术是非常实用的技术,如读/写文件,网络通讯等等。

  • 流(Stream)是指从源节点到目标节点的数据流动。

  • 源节点和目标节点可以是文件、网络、内存、键盘、显示器等等。

  • 源节点的数据流称为输入流(用来读取数据)。

  • 目标节点的数据流称为输出流(用来写入数据)。

请添加图片描述

流的分类
  • I/O流类库位于java.io包中。

  • 从传输数据的角度,I/O流又可分为字节流和字符流

请添加图片描述

  • InputStream和OutputStream是字节流的基类, Reader和Writer是字符流的基类。

  • I/O流在文件读写、网络通讯、内存数据交换等方面被广泛应用。

读文件访问
  • 无论是文本文件还是二进制文件,当需要读取文件数据时,需要完成以下步骤:
  1. 使用文件输入流打开指定文件:

    1. 对于文本文件,应使用字符输入流FileReader流
    2. 对于二进制文件,应使用字节输入流FileInputStream流
  2. 读取文件数据

  3. 关闭输入流

写文件访问
  • 无论是文本文件还是二进制文件,当需要将数据写入文件时,需要完成以下步骤:
  1. 使用文件输出流打开指定文件:

    1. 对于文本文件,应使用字符输出流FileWriter流
    2. 对于二进制文件,应使用字节输出流FileOutputStream流
  2. 将数据写入文件

  3. 关闭输出流

  • 在打开一个现有文件的输出流以准备写入数据时,有两种方式可供选择:

    • 以清空方式打开
    • 以添加方式打开
  import java.io.*;
  
  public class TestFileReader {
      public static void main(String[] args) {
          FileReader fr = null;
          try {
              fr = new FileReader(args[0]);
  
              char[] buf = new char[100];
              int n = fr.read(buf);
              while (n != -1)    {                             //-1表示到达文件尾部
                  for (int i = 0; i < n; i++ ){
                      System.out.print(buf[i]);
                  }
                  n = fr.read(buf);
              }
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              if (fr != null)    {
                  try {
                      fr.close();
                  } catch (IOException e) {
                  }
              }
          }
      }
  }
文本文件中的<回车>-<换行>序列
  • 在Windows系统中,文本文件每行结尾都有两个不可见的特殊字符表示该行结束。
  • 这两个字符为<回车>符(ASCII值为13)和<换行>符(ASCII值10 ),称为<回车>-<换行>序列。
  • 在Unix系统中,文本文件每行结尾只有<换行>符。
  • 在Java语言中, <回车>符用’\r’表示,<换行>符用’\n’表示。
  • System.out.println语句,就是在输出一行内容后,继续输出<回车>-<换行>序列,从显示效果上使光标移动下一行开始。
练习
  1. 编写程序TextFile.java,在main方法中,读取TextFile.java文本文件,并将文件内容输出到屏幕上。

  2. 选做:改进该程序,读取文件内容后,在每行开始加上行号,再连同内容一并输出到屏幕上。

    提示:可将读出的char数组转换为StringBuilder,然后在字符串中搜索“\n”,并在其之后插入行号即可。

import java.io.*;

public class TestFileWriter {
    public static void main(String[] args) {
        String[] text = {"这是第1行\r\n",
                         "这是一个TestWriter示例程序\r\n",
                         "需要用FileWriter打开文件\r\n",
                         "访问结束后需要关闭文件\r\n"};
        FileWriter fw = null;

        try {
            fw = new FileWriter(args[0]);

            char[] buf;
            for(String str : text){
                buf = str.toCharArray();
                for (int i = 0; i < str.length(); i++ ){
                    System.out.print(buf[i]);
                }

                fw.write(buf, 0, str.length());
                //或 fw.write(str, 0, str.length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null)    {
                try {
                    fw.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

练习:

  1. 编写程序FileCopy.java,在main方法中,将FileCopy.java复制为FileCopy.java.bak文件;

  2. 查看FileCopy.java.bak文件的内容,验证复制是否正确。

字符流的包装与链接

  • 通常很少使用单个流对象,而是将一系列的流以包装的形式链接起来处理数据。

  • 包装可以在不改变被包装流的前提下,获得更强的流处理功能。

  • 典型的字符输入流/输出流的链接如下:

请添加图片描述请添加图片描述

public class TestBufferedReader {
    public static void main(String[] args) {
        FileReader fr = null;
        BufferedReader br = null;

        try {
            fr = new FileReader(args[0]);
            br = new BufferedReader(fr);

            String line = br.readLine();
            while (line != null)    {
                System.out.println(line);
                line = br.readLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null)    {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

练习:

  1. 改写程序TextFile.java,使用Buffered包装形式读取TextFile.java文本文件,为每行加上行号,再连同内容一并输出到屏幕上。
import java.io.*;

public class TestFileWriter {
    public static void main(String[] args) {
        String[] text = {"这是第1行\r\n",
                         "这是一个TestWriter示例程序\r\n",
                         "需要用FileWriter打开文件\r\n",
                         "访问结束后需要关闭文件\r\n"};
        FileWriter fw = null;

        try {
            fw = new FileWriter(args[0]);

            char[] buf;
            for(String str : text){
                buf = str.toCharArray();
                for (int i = 0; i < str.length(); i++ ){
                    System.out.print(buf[i]);
                }

                fw.write(buf, 0, str.length());
                //或 fw.write(str, 0, str.length());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fw != null)    {
                try {
                    fw.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

练习:

  1. 改写程序FileCopy.java,使用Buffered包装形式,将FileCopy.java复制为FileCopy.java.bak文件;

  2. 查看FileCopy.java.bak文件的内容,验证复制是否正确。

字节流的包装与链接

  • 与字符流一样,字节流也可以实现包装和链接,并且可以因此带来更为强大的功能,例如读写基本类型数据、读写对象等。

  • 典型的字节输入流/输出流的链接如下:

请添加图片描述请添加图片描述

public class TestOutputStream {
    public static void main(String[] args) {
        int num = 56789;
        float f = 12345.56f;
        boolean flag = true;
        String str = "the quick fox jumps over the lazy dog.";

        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        ObjectOutputStream oos = null;

        try {
            fos = new FileOutputStream(args[0]);
            bos = new BufferedOutputStream(fos);
            oos = new ObjectOutputStream(bos);

            oos.writeInt(num);
            oos.writeFloat(f);
            oos.writeBoolean(flag);
            oos.writeUTF(str);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null)    {
                try {
                    oos.close();
                } catch (IOException e) {
                }
            }
        }
    }
}
public class TestInputStream {
    public static void main(String[] args) {
        FileInputStream fis = null;
        ObjectInputStream ois = null;

        try {
            fis = new FileInputStream(args[0]);
            ois = new ObjectInputStream(fis);

            int num = ois.readInt();
            float f = ois.readFloat();
            boolean flag = ois.readBoolean();
            String str = ois.readUTF();

            System.out.println(num);
            System.out.println(f);
            System.out.println(flag);
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ois != null)    {
                try {
                    ois.close();
                } catch (IOException e) {
                }
            }
        }
    }
}

练习:

  1. 编写程序,在main方法中,随机生成0-100的整数50个,将这50个数以二进制形式写到文件中;

  2. 编写另一个程序,在main方法中,将前面所生成的50个数从文件中读取出来并打印。

转换流
  • OutputStreamWriter:将字符流转成字节流
  • InputStreamReader:把字节流转成字符流
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值