JAVA的学习心路历程之JDK基础入门(上)

       任务需要,需要我学java调用linux下的动态库,于是搜寻java知识更新这篇。

        从我上大学起我就听别人说JAVA,不不,应该是初中,那时候流行带键盘的智能手机,里面有好些个游戏都是JAVA写的,可见JAVA有多变态。但是本人却没学过JAVA,或者说我对JAVA不感兴趣,反而一致在研究C语言系列的代码。但是有句话怎么说来着,你不学习它,它就学习你,哈哈哈哈哈。那么索性将自己学习JAVA的过程记录下来,这又将是一个怎么艰辛的过程我不敢想象,但是工作不会体悟你,它只会逼你进步,逼你成长,逼你接受。这怎么又emo了,振作起来啊,你还什么都没有呢!

JAVA安装:JDK17安装教程及环境变量配置_mushol的博客-CSDN博客_jdk17

JAVA IDE:IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBrains

JAVA IDE 配置JAVA:Java小白必会!Intellij IDEA安装、配置及使用详细教程_一一哥Sun的博客-CSDN博客_idea安装配置教程

目录

一、java.lang包

1.1 java api简介

二、日期和随机数(java.util和java.time)

2.1 Data类 

2.2 Calendar 类

2.3 java.time包 

2.4 Random类

三、集合框架

3.1 Collection 接口 

         3.2 Map 接口 

3.3 List 接口与 ArrayList 类

3.4 Set 接口和 HashSet 类

3.5 HashMap 类

四、字节流 

4.1 I/O 流概述

4.2 基类:InputStream 和 OutputStream

4.3 文件流

4.4 缓冲流

4.5  数据流

4.6  标准流、内存读写流、顺序输入流

五、总结


整个过程用到的代码在我的gitcode中,直接git clone就行了。

git clone https://gitcode.net/weixin_44120785/myleranjava.git

一、java.lang包

        java.lang 包内的包装类以及 String 类、Math 类、Class 类、Object 类的相关知识。

1.1 java api简介

        java api非常庞大,有些api我们必须掌握,这算是java的基础。

        在程序中,java.lang 包并不需要像其他包一样需要 import 关键字进行引入。系统会自动加载,所以我们可以直接取用其中的所有类,这有点像Python的基础类,同样不需要要import,也能用一些库。下面我们就来详细地学习一下 java.lang 包吧。

        我们都知道 java 是一门面向对象的语言,类将方法和属性封装起来,这样就可以创建和处理相同方法和属性的对象了。但是 java 中的基本数据类型却不是面向对象的,不能定义基本类型的对象。 java 为每个基本类型都提供了包装类:

原始数据类型包装类
byte(字节)Byte
char(字符)Character
int(整型)Integer
long (长整型)Long
float(浮点型)Float
double (双精度)Double
boolean (布尔)Boolean
short(短整型)Short

        好家伙,这是给C语言的基本类封装了一层。在这八个类名中,除了 Integer 和 Character 类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写。

        Integer 类:

        java.lang 包中的 Integer 类、Long 类和 Short 类都是 Number 的子类,他们的区别在于不同子类里面封装着不同的数据类型,比如 Integer 类包装了一个基本类型 int。其包含的方法基本相同。我们以 Integer 类为例。 Integer 构造方法有两种:

Integer a = new Integer(10);//以 int 型变量作为参数创建 Integer 对象
Integer a = new Integer("10")//以 String 型变量作为参数创建 Integer 对象

        好高级,注意第二个“10”是string类型,直接转为Number类型。在C++中,这不得自己写个函数做个转换啥的,JAVA好方便。下面列举一下 Integer 的常用方法。

方法返回值功能描述
byteValue()byte以 byte 类型返回该 Integer 的值
compareTo(Integer anotherInteger)int在数字上比较 Integer 对象。如果这两个值相等,则返回 0;如果调用对象的数值小于 anotherInteger 的数值,则返回负值;如果调用对象的数值大于 anotherInteger 的数值,则返回正值
equals(Object IntegerObj)boolean比较此对象与指定对象是否相等
intValue()int以 int 型返回此 Integer 对象
shortValue()short以 short 型返回此 Integer 对象
longValue()long以 long 型返回此 Integer 对象
floatValue()float以 float 型返回此 Integer 对象
doubleValue()double以 double 型返回此 Integer 对象
toString()String返回一个表示该 Integer 值的 String 对象
valueOf(String str)Integer返回保存指定的 String 值的 Integer 对象
parseInt(String str)int将字符串参数作为有符号的十进制整数进行解析

二、日期和随机数(java.util和java.time)

        主要内容为java.util 中的 Date 类、Calendar 类,Random 类以及 java.time 包中的 LocalTime 类。java.util 包提供了一些实用的方法和数据结构,这就有点像c++中的algorithm、vector、queue、map库,把它引进来我们可以直接使用封装好的数据结构和算法。java.time 包是 java8 新提供的包,里面对时间和日期提供了新的 api,弥补了 java.util 包对日期和时间操作的不足。目前我的c++还在使用时间戳来判定时间。

2.1 Data类 

        Date 类表示日期和时间,里面封装了操作日期和时间的方法。Date 类经常用来获取系统当前时间。我们来看看类 Date 中定义的未过时的构造方法:        

构造方法说明
Date()构造一个 Date 对象并对其进行初始化以反映当前时间
Date(long date)构造一个 Date 对象,并根据相对于 GMT 1970 年 1 月 1 日 00:00:00 的毫秒数对其进行初始化

eg:

import javax.xml.crypto.Data;
import java.util.*;
public class DateDemo {
    public static void main(String[] args) throws ClassNotFoundException{
        String strDate,strTime = "";
        Date objDate = new Date();
        System.out.println("today: "+objDate);
        long time = objDate.getTime();
        System.out.println("自 1970 年 1 月 1 日起以毫秒为单位的时间(GMT):" + time);
        strDate = objDate.toString();
        //提取 GMT 时间
        System.out.println("strDate :" + strDate);
        strTime = strDate.substring(11,(strDate.length() - 4));
        //按小时、分钟和秒提取时间
        strTime = "时间:" + strTime.substring(0,8);
        System.out.println(strTime);
    }
}

输出结果:

/Library/Java/JavaVirtualMachines/jdk-17.0.5.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=58812:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/liubowen/IdeaProjects/javatest/out/production/javatest DateDemo
today: Sun Nov 20 14:47:50 CST 2022
自 1970 年 1 月 1 日起以毫秒为单位的时间(GMT):1668926870559
strDate :Sun Nov 20 14:47:50 CST 2022
时间:14:47:50

Process finished with exit code 0

 2.2 Calendar 类

        类 Calendar 来完成允许用年、月、日、时、分、秒来解释日期,类 DateFormat 来完成对表示日期的字符串进行格式化和句法分析。DateFormat 是 java.text 包中的一个类。与 Date 类有所不同的是,DateFormat 类可以接受用各种语言和不同习惯表示的日期字符串。

eg:

import java.util.*;
import java.text.*;

public class CalendarDemo
{
  public static void main(String[] args)
  {

    // 得到日期格式对象
    // Date date = fmt.parse(strDateMake);

    System.out.println("完整显示日期时间:");
    // 字符串转换日期格式
    DateFormat fdate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String str =fdate.format(new Date());
    System.out.println(str);

    // 创建 Calendar 对象
    Calendar calendar = Calendar.getInstance();
    // 初始化 Calendar 对象,但并不必要,除非需要重置时间
    calendar.setTime(new Date());

    // 显示年份
    System.out.println("年: " + calendar.get(Calendar.YEAR));

    // 显示月份 (从 0 开始,实际显示要加一)
    System.out.println("月: " + calendar.get(Calendar.MONTH));

    // 当前分钟数
    System.out.println("分钟: " + calendar.get(Calendar.MINUTE));

    // 今年的第 N 天
    System.out.println("今年的第 " + calendar.get(Calendar.DAY_OF_YEAR) + "天");

    // 本月第 N 天
    System.out.println("本月的第 " + calendar.get(Calendar.DAY_OF_MONTH) + "天");

    // 3 小时以后
    calendar.add(Calendar.HOUR_OF_DAY, 3);
    System.out.println("三小时以后的时间: " + calendar.getTime());
    // 格式化显示
    str = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS")).format(calendar.getTime());
    System.out.println(str);

    // 重置 Calendar 显示当前时间
    calendar.setTime(new Date());
    str = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS")).format(calendar.getTime());
    System.out.println(str);

    // 创建一个 Calendar 用于比较时间
    Calendar calendarNew = Calendar.getInstance();

    // 设定为 5 小时以前,后者大,显示 -1
    calendarNew.add(Calendar.HOUR, -5);
    System.out.println("时间比较:" + calendarNew.compareTo(calendar));

    // 设定 7 小时以后,前者大,显示 1
    calendarNew.add(Calendar.HOUR, +7);
    System.out.println("时间比较:" + calendarNew.compareTo(calendar));

    // 退回 2 小时,时间相同,显示 0
    calendarNew.add(Calendar.HOUR, -2);

    // calendarNew 创建时间点
    System.out.println((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS")).format(calendarNew.getTime()));
    // calendar 创建时间点
    System.out.println((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS")).format(calendar.getTime()));
    System.out.println("时间比较:" + calendarNew.compareTo(calendar));
  }
}

2.3 java.time包 

        因为 java8 之前的日期和时间 api 饱受诟病,比如线程安全问题,比如 Date 的月份是从 0 开始的!而 java.time 包中将月份封装成为了枚举类型。接下来来看看如何使用这个新的时间报。

        首先了解一下 LocalTime 类,LocalTime 类是一个不可变类(也就是用 final 修饰的类),和 String 类一样,所以它是线程安全的。除了 LocalTime 还有 LocalDate(日期)、LocalDateTime(日期和时间)等,他们的使用方式都差不多。下面来实际编写一下。

eg:

import java.time.*;
import java.time.temporal.ChronoUnit;

public class TimeDemo {
    public static void main(String[] args) {
        // 获得当前的日期和时间
        LocalDateTime currentTime = LocalDateTime.now();
        System.out.println("current date and time: " + currentTime);

        // 输出当前时间的本地值(本时区)
        LocalDate date1 = currentTime.toLocalDate();
        System.out.println("local date: " + date1);

        Month month = currentTime.getMonth();
        int day = currentTime.getDayOfMonth();
        int seconds = currentTime.getSecond();

        // 由当前时间对象获得各个字段,输出结果
        System.out.println("month: " + month +"day: " + day +"seconds: " + seconds);

        // 由当前时间附带月份和年再输出
        LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
        System.out.println("date 2: " + date2);

        // 输出 2016 年圣诞节的日期
        LocalDate date3 = LocalDate.of(2016, Month.DECEMBER, 25);
        System.out.println("date 3: " + date3);

        // 输出新闻联播的开始时间
        LocalTime date4 = LocalTime.of(19, 00);
        System.out.println("date 4: " + date4);

        // 转化为字符串,再输出
        LocalTime date5 = LocalTime.parse("20:15:30");
        System.out.println("date 5: " + date5);

        // 将字符串代表的时区信息转化
        ZonedDateTime date6 = ZonedDateTime.parse("2016-04-20T19:22:15+01:30[Europe/Paris]");
        System.out.println("date1: " + date6);

        // 设定地区 ID 为亚洲的加尔各答(位于印度),并输出
        ZoneId id = ZoneId.of("Asia/Kolkata");
        System.out.println("ZoneId: " + id);

        // 获得系统默认的当前地区并输出
        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("CurrentZone: " + currentZone);

        // 获得当前的日期并输出
        LocalDate today = LocalDate.now();
        System.out.println("Current date: " + today);

        // 在当前日期的基础上增加两周时间再输出
        LocalDate nextWeek = today.plus(2, ChronoUnit.WEEKS);
        System.out.println("two weeks after now: " + nextWeek);

        // 在当前日期的基础上增加 6 个月的时间再输出
        LocalDate nextMonth = today.plus(6, ChronoUnit.MONTHS);
        System.out.println("6 months after now: " + nextMonth);

        // 在当前日期的基础上增加 5 年的时间再输出
        LocalDate nextYear = today.plus(5, ChronoUnit.YEARS);
        System.out.println("5 years after now: " + nextYear);

        // 在当前日期的基础上增加 20 年的时间再输出(一个 Decade 为 10 年)
        LocalDate nextDecade = today.plus(2, ChronoUnit.DECADES);
        System.out.println("20 years after now: " + nextDecade);
    }
}

2.4 Random类

        Java 实用工具类库中的类 java.util.Random 提供了产生各种类型随机数的方法。它可以产生 int、long、float、double 以及 Gaussian 等类型的随机数。这也是它与 java.lang.Math 中的方法 random() 最大的不同之处,后者只产生 double 型的随机数。当然,这一切都是伪随机,严格讲人类制造不出真随机,这是由造物主决定的事情。

        这一部分可以对标python中的random,记得以前在训练神经网络的时候必须给内部的参数比如W和b一个初始值,并且要保证每次的初始值都相同以方便对比改进方法,所以要加seed种子值,加上种子每次初始化就相同了。

构造方法说明
Random()产生一个随机数需要基值,这里将系统时间作为 seed
Random(long seed)使用单个 long 种子创建一个新的随机数生成器

         一些方法:

//该方法是设定基值 seed
public synchronized void setSeed(long seed)

//该方法是产生一个整型随机数
public int nextInt()

//该方法是产生一个 long 型随机数
public long nextLong()

//该方法是产生一个 Float 型随机数
public float nextFloat()

//该方法是产生一个 Double 型随机数
public double nextDouble()

//该方法是产生一个 double 型的 Gaussian 随机数
public synchronized double nextGaussian()

/*
synchronized  是 Java 语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码
*/

        参考在线文档:

  在线文档-jdk-zh

三、集合框架

        Java 集合框架里的接口:Collection 接口、Map 接口、List 和 ArrayList、Set 和 HashSet、HashMap。       

        集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大内容:对外的接口、接口的实现和对集合运算的算法。这部分的设计我感觉好像C++中的STL啊,STL六大组建:容器、算法、迭代器、仿函数、配接器和配置器,C++的STL具体看下面:

STL六大组件_盐焗咸鱼的博客-CSDN博客

        集合框架关系图:

3.1 Collection 接口 

        因为集合框架中的很多类功能是相似的,所以我们用接口来规范类。Collection 接口是 java 集合框架里的一个根接口。它也是 List、Set 和 Queue 接口的父接口。Collection 接口中定义了可用于操作 List、Set 和 Queue 的方法——增删改查。

方法返回值说明
add(E e)boolean向 collection 的尾部追加指定的元素(可选操作)
addAll(Collection<? extend E> c)boolean将指定 collection 中的所有元素都添加到此 collection 中(可选操作)
clear()void移除此 collection 中的所有元素(可选操作)
contains(Object o)boolean如果此 collection 包含指定的元素,则返回 true
containsAll(Collection<?> c)boolean如果此 collection 包含指定 collection 的所有元素,则返回 true
equals(Object o)boolean比较此 collection 与指定对象是否相等
hashCode()int返回此 collection 的哈希码值
isEmpty()boolean如果此 collection 不包含元素,则返回 true
iterator()Iterator返回在此 collection 的元素上进行迭代的迭代器
remove(Object o)boolean移除此 collection 中出现的首个指定元素(可选操作)
removeAll(Collection<?> c)boolean移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)
retainAll(Collection<?> c)boolean仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)
size()int返回此 collection 中的元素数
toArray()Object[]返回包含此 collection 中所有元素的数组
toArray(T[] a)T[]返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同

3.2 Map 接口 

        Java map与C++map概念上没有异议:

        

        value 可以存储任意类型的对象,我们可以根据 key 键快速查找 value。Map 中的键/值对以 Entry 类型的对象实例形式存在。

方法返回值说明
clear()void从此映射中移除所用映射关系(可选操作)
containsKey(Object key)boolean如果此映射包含指定键的映射关系,则返回 true
containsValue(Object value)boolean如果此映射将一个或多个键映射到指定值,则返回 true
entrySet()Set<Map.Entry<K,V>>返回此映射中包含的映射关系的 Set 视图
equals(Object o)boolean比较指定的对象与此映射是否相等
get(Object key)V返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
hashCode()int返回此映射的哈希码值
isEmpty()boolean如果此映射未包含键-值映射关系,则返回 true
keySet()Set返回此映射中包含的键的 Set 视图
put(K key, V value)V将指定的值与此映射中的指定键关联(可选操作)
putAll(Map<? extends K, ? extends V> m)void从指定映射中将所有映射关系复制到此映射中(可选操作)
remove(Object key)V如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
sizeint返回此映射中的键-值映射关系数
values()Collection返回此映射中包含的值的 Collection 视图

 3.3 List 接口与 ArrayList 类

        List 是一个接口,不能实例化,需要一个具体类来实现实例化。List 集合中的对象按照一定的顺序排放,里面的内容可以重复。List 接口实现的类有:ArrayList(实现动态数组),Vector(实现动态数组),LinkedList(实现链表),Stack(实现堆栈)。

        List 在 Collection 基础上增加的方法:

方法返回值说明
add(int index, E element)void在列表的指定位置插入指定元素(可选操作)
addAll(int index, Collection<? extends E> c)boolean将指定 collection 中的所有元素都插入到列表中的指定位置(可选操作)
get(int index)E返回列表中指定位置的元素
indexOf(Object o)int返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1
lastIndexOf(Object o)int返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1
listIterator()ListIterator返回此列表元素的列表迭代器(按适当顺序)
listIterator(int index)ListIterator返回此列表元素的列表迭代器(按适当顺序),从列表的指定位置开始
remove(int index)E移除列表中指定位置的元素(可选操作)
set(int index, E element)E用指定元素替换列表中指定位置的元素(可选操作)
subList(int fromIndex, int toIndex)List返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图

        ArrayList 类实现一个可增长的动态数组,它可以存储不同类型的对象,而数组则只能存放特定数据类型的值。

        我们通过实际的例子来学习 ArrayList 吧。学校的教务系统会对学生进行统一的管理,每一个学生都会有一个学号和学生姓名,我们在维护整个系统的时候,大多数操作是对学生的添加、插入、删除、修改等操作。

        先创建一个学生类,java的类的形式也很像C++啊,类中同样的成员变量和成员方法,同样有权限的修饰。

/*
 * 学生类
 */

public class Student {
    public String id;
    public String name;
    public Student(String id, String name){
        this.id = id;
        this.name = name;
    }

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

        再创建一个学生列表,来管理学生:

import java.util.*;

public class ListTest {

    //集合后面的<>代表泛型的意思
    //泛型是规定了集合元素的类型
    //我们以后会详细讲到
    /**
     * 用于存放学生的 List
     */
    public List<Student> students;

    public ListTest() {
        this.students = new ArrayList<Student>();
    }

    /**
     * 用于往 students 中添加学生
     */
    public void testAdd() {
        // 创建一个学生对象,并通过调用 add 方法,添加到学生管理 List 中
        Student st1 = new Student("1", "张三");
        students.add(st1);

        // 取出 List 中的 Student 对象
        Student temp = students.get(0);
        System.out.println("添加了学生:" + temp.id + ":" + temp.name);

        Student st2 = new Student("2", "李四");
        students.add(0, st2);
        Student temp2 = students.get(0);
        System.out.println("添加了学生:" + temp2.id + ":" + temp2.name);

        // 对象数组的形式添加
        Student[] student = {new Student("3", "王五"), new Student("4", "马六")};

        // Arrays 类包含用来操作数组(比如排序和搜索)的各种方法,asList() 方法用来返回一个受指定数组支持的固定大小的列表
        students.addAll(Arrays.asList(student));
        Student temp3 = students.get(2);
        Student temp4 = students.get(3);
        System.out.println("添加了学生:" + temp3.id + ":" + temp3.name);
        System.out.println("添加了学生:" + temp4.id + ":" + temp4.name);
        Student[] student2 = {new Student("5", "周七"), new Student("6", "赵八")};
        students.addAll(2, Arrays.asList(student2));
        Student temp5 = students.get(2);
        Student temp6 = students.get(3);
        System.out.println("添加了学生:" + temp5.id + ":" + temp5.name);
        System.out.println("添加了学生:" + temp6.id + ":" + temp6.name);
    }

    /**
     * 取得 List 中的元素的方法
     */
    public void testGet() {
        int size = students.size();
        for (int i = 0; i < size; i++) {
            Student st = students.get(i);
            System.out.println("学生:" + st.id + ":" + st.name);

        }
    }

    /**
     * 通过迭代器来遍历
     */
    // 迭代器的工作是遍历并选择序列中的对象,Java 中 Iterator 只能单向移动
    public void testIterator() {
        // 通过集合的 iterator 方法,取得迭代器实例
        Iterator<Student> it = students.iterator();
        System.out.println("有如下学生(通过迭代器访问):");
        while (it.hasNext()) {

            Student st = it.next();
            System.out.println("学生" + st.id + ":" + st.name);
        }
    }

    /**
     * 通过 for each 方法访问集合元素
     *
     */
    public void testForEach() {
        System.out.println("有如下学生(通过 for each):");
        for (Student obj : students) {
            Student st = obj;
            System.out.println("学生:" + st.id + ":" + st.name);
        }
        //使用 java8 Stream 将学生排序后输出
        students.stream()//创建 Stream
                //通过学生 id 排序
                .sorted(Comparator.comparing(x -> x.id))
                //输出
                .forEach(System.out::println);
    }

    /**
     * 修改 List 中的元素
     *
     */
    public void testModify() {
        students.set(4, new Student("3", "吴酒"));
    }

    /**
     * 删除 List 中的元素
     *
     */
    public void testRemove() {
        Student st = students.get(4);
        System.out.println("我是学生:" + st.id + ":" + st.name + ",我即将被删除");
        students.remove(st);
        System.out.println("成功删除学生!");
        testForEach();

    }

    public static void main(String[] args) {
        ListTest lt = new ListTest();
        lt.testAdd();
        lt.testGet();
        lt.testIterator();
        lt.testModify();
        lt.testForEach();
        lt.testRemove();

    }
}

        List 有两种基本的类型,除了 ArrayList 外,还有 LinkedList,LinkedList 类用于创建链表数据结构,两者的对比如下:

  • ArrayList:它擅长于随机访问元素,但是插入和移除元素时较慢。
  • LinkedList:它通过代价较低的在 List 中进行插入和删除操作,提供了优化的顺序访问,它在随机访问方面相对较慢,但是它的特性集较 ArrayList 更大。

3.4 Set 接口和 HashSet 类

        Set 接口也是 Collection 接口的子接口,它有一个很重要也是很常用的实现类——HashSet,Set 是元素无序并且不包含重复元素的 collection(List 可以重复),被称为集。

        HashSet 由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。

        因为项目组的组长由一个老师担任,首先我们来创建一个 PD 类。

import java.util.HashSet;
import java.util.Set;
/*
 * 项目组长类
 */
public class PD {

    public String id;
    public String name;
    //集合后面的<>代表泛型的意思
    //泛型是规定了集合元素的类型
    //我们以后会详细讲到
    public Set<Student> students;
    public PD(String id, String name){
        this.id = id;
        this.name = name;
        this.students = new HashSet<Student>();
    }
}

        接下来我们便创建一个 SetTest 类,用来管理项目成员。 

3.5 HashMap 类

        HashMap 是基于哈希表的 Map 接口的一个重要实现类。HashMap 中的 Entry 对象是无序排列的,Key 值和 value 值都可以为 null,但是一个 HashMap 只能有一个 key 值为 null 的映射(key 值不可重复)。

        下面我们通过代码来学习 Map 中的方法吧。同学们都有过选课经历吧,我们就用 Map 来管理课程吧。创建一个 Course 类:

public class Course {
    public String id;
    public String name;
    public Course(String id, String name){
        this.id = id;
        this.name = name;
    }
}

        MapTest类:

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;

public class MapTest {

    /**
     * 用来承装课程类型对象
     */
    public Map<String, Course> courses;

    /**
     * 在构造器中初始化 courses 属性
     * @param args
     */
    public MapTest() {
        this.courses = new HashMap<String, Course>();
    }

    /**
     * 测试添加:输入课程 ID,判断是否被占用
     * 若未被占用,输入课程名称,创建新课程对象
     * 并且添加到 courses 中
     * @param args
     */
    public void testPut() {
        //创建一个 Scanner 对象,用来获取输入的课程 ID 和名称
        Scanner console = new Scanner(System.in);

        for(int i = 0; i < 3; i++) {
            System.out.println("请输入课程 ID:");
            String ID = console.next();
            //判断该 ID 是否被占用
            Course cr = courses.get(ID);
            if(cr == null){
                //提示输入课程名称
                System.out.println("请输入课程名称:");
                String name = console.next();
                //创建新的课程对象
                Course newCourse = new Course(ID,name);
                //通过调用 courses 的 put 方法,添加 ID-课程映射
                courses.put(ID, newCourse);
                System.out.println("成功添加课程:" + courses.get(ID).name);
            }
            else {
                System.out.println("该课程 ID 已被占用");
                continue;
            }
        }
    }

    /**
     * 测试 Map 的 keySet 方法
     * @param args
     */

    public void testKeySet() {
        //通过 keySet 方法,返回 Map 中的所有键的 Set 集合
        Set<String> keySet = courses.keySet();
        //遍历 keySet,取得每一个键,在调用 get 方法取得每个键对应的 value
        for(String crID: keySet) {
            Course cr = courses.get(crID);
            if(cr != null){
                System.out.println("课程:" + cr.name);
            }
        }
    }

    /**
     * 测试删除 Map 中的映射
     * @param args
     */
    public void testRemove() {
        //获取从键盘输入的待删除课程 ID 字符串
        Scanner console = new Scanner(System.in);
        while(true){
            //提示输出待删除的课程 ID
            System.out.println("请输入要删除的课程 ID!");
            String ID = console.next();
            //判断该 ID 是否对应的课程对象
            Course cr = courses.get(ID);
            if(cr == null) {
                //提示输入的 ID 并不存在
                System.out.println("该 ID 不存在!");
                continue;
            }
            courses.remove(ID);
            System.out.println("成功删除课程" + cr.name);
            break;
        }
    }

    /**
     * 通过 entrySet 方法来遍历 Map
     * @param args
     */
    public void testEntrySet() {
        //通过 entrySet 方法,返回 Map 中的所有键值对
        Set<Entry<String,Course>> entrySet = courses.entrySet();
        for(Entry<String,Course> entry: entrySet) {
            System.out.println("取得键:" + entry.getKey());
            System.out.println("对应的值为:" + entry.getValue().name);
        }
    }

    /**
     * 利用 put 方法修改 Map 中的已有映射
     * @param args
     */
    public void testModify(){
        //提示输入要修改的课程 ID
        System.out.println("请输入要修改的课程 ID:");
        //创建一个 Scanner 对象,去获取从键盘上输入的课程 ID 字符串
        Scanner console = new Scanner(System.in);
        while(true) {
            //取得从键盘输入的课程 ID
            String crID = console.next();
            //从 courses 中查找该课程 ID 对应的对象
            Course course = courses.get(crID);
            if(course == null) {
                System.out.println("该 ID 不存在!请重新输入!");
                continue;
            }
            //提示当前对应的课程对象的名称
            System.out.println("当前该课程 ID,所对应的课程为:" + course.name);
            //提示输入新的课程名称,来修改已有的映射
            System.out.println("请输入新的课程名称:");
            String name = console.next();
            Course newCourse = new Course(crID,name);
            courses.put(crID, newCourse);
            System.out.println("修改成功!");
            break;
        }
    }

    public static void main(String[] args) {
        MapTest mt = new MapTest();
        mt.testPut();
        mt.testKeySet();
        mt.testRemove();
        mt.testModify();
        mt.testEntrySet();

    }
}

四、字节流 

        字节流分为基类、文件流、过滤流、标准流、内存读写流、顺序输出流等,在java.io 包中。

4.1 I/O 流概述

        大部分程序都需要进行输入/输出处理,比如从键盘读取数据、从屏幕中输出数据、从文件中写数据等等。在 Java 中,把这些不同类型的输入、输出源抽象为流(Stream),而其中输入或输出的数据则称为数据流(Data Stream),用统一的接口表示,从而使程序设计简单明了。

        流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

        流一般分为输入流(Input Stream)和输出流(Output Stream)两类,但这种划分并不是绝对的。比如一个文件,当向其中写数据时,它就是一个输出流;当从其中读取数据时,它就是一个输入流。当然,键盘只是一个输入流,而屏幕则只是一个输出流。(其实我们可以通过一个非常简单的方法来判断,只要是向内存中写入就是输入流,从内存中写出就是输出流)。

        

4.2 基类:InputStream 和 OutputStream

        字节流主要操作 byte 类型数据,以 byte 数组为准,java 中每一种字节流的基本功能依赖于基本类 InputStream 和 Outputstream,他们是抽象类,不能直接使用。字节流能处理所有类型的数据(如图片、avi 等)。有点像C++里的OStream和IStream,都是输入输出流的基类,但是目前不清楚有没有java里面的强大。

        InputStream 是所有表示字节输入流类的基类,继承它的子类要重新定义其中所定义的抽象方法。InputStream 是从装置来源地读取数据的抽象表示,例如 System 中的标准输入流 in 对象就是一个 InputStream 类型的实例。

        我们先来看看 InputStream 类的方法:

方法说明
read()throws IOException从输入流中读取数据的下一个字节(抽象方法)
skip(long n) throws IOException跳过和丢弃此输入流中数据的 n 个字节
available()throws IOException返回流中可用字节数
mark(int readlimit)throws IOException在此输入流中标记当前的位置
reset()throws IOException将此流重新定位到最后一次对此输入流调用 mark 方法时的位置
markSupport()throws IOException测试此输入流是否支持 mark 和 reset 方法
close()throws IOException关闭流

在 InputStream 类中,方法 read() 提供了三种从流中读数据的方法:

  1. int read():从输入流中读一个字节,形成一个 0~255 之间的整数返回(是一个抽象方法)。
  2. int read(byte b[]):从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
  3. int read(byte b[],int off,int len):从输入流中读取长度为 len 的数据,写入数组 b 中从索引 off 开始的位置,并返回读取得字节数。

        对于这三个方法,若返回 -1,表明流结束,否则,返回实际读取的字符数。

        OutputStream 是所有表示字节输出流类的基类。子类要重新定义其中所定义的抽象方法,OutputStream 是用于将数据写入目的地的抽象表示。例如 System 中的标准输出流对象 out 其类型是 java.io.PrintStream,这个类是 OutputStream 的子类。

方法说明
write(int b)throws IOException将指定的字节写入此输出流(抽象方法)
write(byte b[])throws IOException将字节数组中的数据输出到流中
write(byte b[], int off, int len)throws IOException将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
flush()throws IOException刷新此输出流并强制写出所有缓冲的输出字节
close()throws IOException关闭流

eg:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class StreamTest {

    /**
     * 把输入流中的所有内容赋值到输出流中
     * @param in
     * @param out
     * @throws IOException
     */
    public void copy(InputStream in, OutputStream out) throws IOException {
        byte[] buf = new  byte[4096];
        int len = in.read(buf);
        //read 是一个字节一个字节地读,字节流的结尾标志是-1
        while (len != -1){
            out.write(buf, 0, len);
            len = in.read(buf);
        }
    }
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        StreamTest t = new StreamTest();
        System.out.println("输入字符:");
        t.copy(System.in, System.out);
    }

}

4.3 文件流

在 I/O 处理中,最常见的就是对文件的操作。java.io 包中所提供的文件操作类包括:

  1. 用于读写本地文件系统中的文件:FileInputStream 和 FileOutputStream
  2. 描述本地文件系统中的文件或目录:File、FileDescriptor 和 FilenameFilter
  3. 提供对本地文件系统中文件的随机访问支持:RandomAccessFile

        FileInputStream 类用于打开一个输入文件,若要打开的文件不存在,则会产生异常 FileNotFoundException,这是一个非运行时异常,必须捕获或声明抛弃。 FileOutputStream 类用来打开一个输出文件,若要打开的文件不存在,则会创建一个新的文件,否则原文件的内容会被新写入的内容所覆盖。

        在进行文件的读/写操作时,会产生非运行时异常 IOException,必须捕获或声明抛弃(其他的输入/输出流处理时也同样需要进行输入/输出异常处理)。

        文件流的构造方法:

//打开一个以 f 描述的文件作为输入
FileInputStream(File f)

//打开一个文件路径名为 name 的文件作为输入
FileInputStream(String name)

//创建一个以 f 描述的文件作为输出
//如果文件存在,则其内容被清空
FileOutputStream(File f)

//创建一个文件路径名为 name 的文件作为输出
//文件如果已经存在,则其内容被清空
FileOutputStream(String name)

//创建一个文件路径名为 name 的文件作为输出
//文件如果已经存在,则在该输出上输出的内容被接到原有内容之后
FileOutputStream(String name, boolean append)

eg1:

File f1 = new File("file1.txt");
File f2 = new File("file2.txt");
FileInputStream in = new FileInputStream(f1);
FileOutputStream out = new FileOutputStream(f2);

 eg2:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class test {

    public static void main(String[] args) {
        try {
            //inFile 作为输入流的数据文件必须存在,否则抛出异常
            File inFile = new File("/Users/mumutongxue/Documents/file1.txt");

            //file2.txt 没有,系统可以创建,在 workspace 的 Test 项目下可以找到
            File outFile = new File("file2.txt");
            FileInputStream fis = new FileInputStream(inFile);
            FileOutputStream fos = new FileOutputStream(outFile);
            int c;
            while((c = fis.read()) != -1){
                fos.write(c);
            }
            //打开了文件一定要记着关,释放系统资源
            fis.close();
            fos.close();
        }catch(FileNotFoundException e) {
            System.out.println("FileStreamsTest:" + e);
        }catch(IOException e){
            System.err.println("FileStreamTest:" + e);
        }
    }

}

 4.4 缓冲流

         类BufferedInputStream 和 BufferedOutputStream 实现了带缓冲的过滤流,它提供了缓冲机制,把任意的 I/O 流“捆绑”到缓冲流上,可以提高 I/O 流的读取效率。

        在初始化时,除了要指定所连接的 I/O 流之外,还可以指定缓冲区的大小。缺省时是用 32 字节大小的缓冲区;最优的缓冲区大小常依赖于主机操作系统、可使用的内存空间以及机器的配置等;一般缓冲区的大小为内存页或磁盘块等的整数倍。

        BufferedInputStream 的数据成员 buf 是一个位数组,默认为 2048 字节。当读取数据来源时例如文件,BufferedInputStream 会尽量将 buf 填满。当使用 read () 方法时,实际上是先读取 buf 中的数据,而不是直接对数据来源作读取。当 buf 中的数据不足时,BufferedInputStream 才会再实现给定的 InputStream 对象的 read() 方法,从指定的装置中提取数据。

        BufferedOutputStream 的数据成员 buf 是一个位数组,默认为 512 字节。当使用 write() 方法写入数据时,实际上会先将数据写至 buf 中,当 buf 已满时才会实现给定的 OutputStream 对象的 write() 方法,将 buf 数据写至目的地,而不是每次都对目的地作写入的动作。

        构造方法:


//[ ] 里的内容代表选填
BufferedInputStream(InputStream in [, int size])
BufferedOutputStream(OutputStream out [, int size])

        将缓冲流与文件流相接:

FileInputStream in = new FileInputStream("file.txt");
FileOutputStream out = new FileOutputStream("file2.txt");
//设置输入缓冲区大小为 256 字节
BufferedInputStream bin = new BufferedInputStream(in,256)
BufferedOutputStream bout = new BufferedOutputStream(out,256)
int len;
byte bArray[] = new byte[256];
len = bin.read(bArray); //len 中得到的是实际读取的长度,bArray 中得到的是数据

        对于 BufferedOutputStream,只有缓冲区满时,才会将数据真正送到输出流,但可以使用 flush() 方法人为地将尚未填满的缓冲区中的数据送出。

        copy的例子:

public void copy(InputStream in, OutputStream out) throws IOException {
    out = new BufferedOutputStream(out, 4096);
    byte[] buf = new byte[4096];
    int len = in.read(buf);
    while (len != -1) {
    out.write(buf, 0, len);
    len = in.read(buf);
    }
    //最后一次读取得数据可能不到 4096 字节
    out.flush();
}

4.5  数据流

        接口 DataInput 和 DataOutput,设计了一种较为高级的数据输入输出方式:除了可处理字节和字节数组外,还可以处理 int、float、boolean 等基本数据类型,这些数据在文件中的表示方式和它们在内存中的一样,无须转换。如read、readInt、readByte、write、writechar。

方法返回值说明
readBoolean()boolean
readByte()byte
readShort()short
readChar()char
readInt()int
readLong()long
readDouble()double
readFloat()float
readUnsignedByte()int
readUnsignedShort()int
readFully(byte[] b)void从输入流中读取一些字节,并将它们存储在缓冲区数组 b 中
readFully(byte[] b, int off,int len)void从输入流中读取 len 个字节
skipBytes(int n)int与 InputStream.skip 等价
readUTF()String按照 UTF-8 形式从输入中读取字符串
readLine()String按回车 (\r) 换行 (\n) 为分割符读取一行字符串,不完全支持 UNICODE
writeBoolean(boolean v)void
writeByte(int v)void
writeShort(int v)void
writeChar(int v)void
writeInt(int v)void
writeLong(long v)void
writeFloat(float v)void
writeDouble(double v)void
write(byte[] b)void与 OutputStream.write 同义
write(byte[] b, int off, int len)void与 OutputStream.write 同义
write(int b)void与 OutputStream.write 同义
writeBytes(String s)void只输出每个字符的低 8 位;不完全支持 UNICODE
writeChars(String s)void每个字符在输出中都占两个字节

 数据流类 DataInputStream 和 DataOutputStream 的处理对象除了是字节或字节数组外,还可以实现对文件的不同数据类型的读写:

  1. 分别实现了 DataInput 和 DataOutput 接口。
  2. 在提供字节流的读写手段同时,以统一的形式向输入流中写入 boolean,int,long,double 等基本数据类型,并可以再次把基本数据类型的值读取回来。
  3. 提供了字符串读写的手段。

        数据流可以连接一个已经建立好的数据对象,例如网络连接、文件等。数据流可以通过如下方式建立:

FileInputStream fis = new FileInputStream("file1.txt");
FileOutputStream fos = new FileOutputStream("file2.txt");
DataInputStream dis = new DataInputStream(fis);
DataOutputStream dos = new DataOutputStream(fos);

        它的用法示例:

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataStream {

    public static void main(String[] args) throws IOException{
        // TODO Auto-generated method stub
        //向文件 a.txt 写入
        FileOutputStream fos = new FileOutputStream("a.txt");
        DataOutputStream dos = new DataOutputStream(fos);
        try {
            dos.writeBoolean(true);
            dos.writeByte((byte)123);
            dos.writeChar('J');
            dos.writeDouble(3.1415926);
            dos.writeFloat(2.122f);
            dos.writeInt(123);
        }
        finally {
            dos.close();
        }
        //从文件 a.txt 读出
        FileInputStream fis = new FileInputStream("a.txt");
        DataInputStream dis = new DataInputStream(fis);
        try {
            System.out.println("\t" + dis.readBoolean());
            System.out.println("\t" + dis.readByte());
            System.out.println("\t" + dis.readChar());
            System.out.println("\t" + dis.readDouble());
            System.out.println("\t" + dis.readFloat());
            System.out.println("\t" + dis.readInt());
        }
        finally {
            dis.close();
        }
    }

}

4.6  标准流、内存读写流、顺序输入流

标准流

        语言包 java.lang 中的 System 类管理标准输入/输出流和错误流。

    System.in 从 InputStream 中继承而来,用于从标准输入设备中获取输入数据(通常是键盘) System.out 从 PrintStream 中继承而来,把输入送到缺省的显示设备(通常是显示器) System.err 也是从 PrintStream 中继承而来,把错误信息送到缺省的显示设备(通常是显示器)

        每当 main 方法被执行时,就会自动生产上述三个对象。这里就不再写代码验证了。

内存读写流

        为了支持在内存上的 I/O,java.io 中提供了类:ByteArrayInputStream、ByteArrayOutputStream 和 StringBufferInputStream

  1. ByteArrayInputStream 可以从指定的字节数组中读取数据。
  2. ByteArrayOutputStream 中提供了缓冲区可以存放数据(缓冲区大小可以在构造方法中设定,缺省为 32),可以用 write() 方法向其中写入数据,然后用 toByteArray() 方法将缓冲区中的有效字节写到字节数组中去。size() 方法可以知道写入的字节数,reset() 可以丢弃所有内容。
  3. StringBufferInputStream 与 ByteArrayInputStream 相类似,不同点在于它是从字符缓冲区 StringBuffer 中读取 16 位的 Unicode 数据,而不是 8 位的字节数据(已被 StringReader 取代)。

        这里只做简要的介绍,有兴趣的同学可以查看一下这些类里具体的方法。

顺序输入流

        java.io 中提供了类 SequenceInputStream,使应用程序可以将几个输入流顺序连接起来。顺序输入流提供了将多个不同的输入流统一为一个输入流的功能,这使得程序可能变得更加简洁。

        例如:

FileInputStream f1,f2;
String s;
f1 = new FileInputStream("file1.txt");
f2 = new FileInputStream("file2.txt");
SequenceInputStream fs = new SequenceInputStream(f1,f2);
DataInputStream ds = new DataInputStream(fs);
while((s = ds.readLine()) != null) {
    System.out.println(s);
}
 

五、总结

        这次迭代有个任务是在Linux上使用java读取.so文件,也就是linux动态库文件,也就是java 调用C生成的so,调用其中的方法,所以有了这篇无比基础的java学习。总的来看,java的书写要求很规范,不会莫名出发错误,甚至写起来有点写python的感觉,尽管二者不相干,这也能够解释为什么这么多人学Java,你无需在复杂的语法和计算机底层上过分劳累,只需关注业务逻辑来编写设计代码就好了。对于C++开发者来说,Java在形式上较比其他语言并无特殊,都是被封装的更加高级的语言,所以C++er学起来并不是很吃力,这也印证了其他开发对其情况的说法。但是Java在移动端、网页等基础设施的建设上一度蚕食C++,这是没有办法的事情,就如同Python因为深度学习和数值计算而火了起来一样,你不能要求所有开发者都去搞明白底层逻辑,他们只是在使用工具而已,哪个好用就用哪个。但是,计算机的性能并不是线性提升的,目前CPU工艺就在nm级别了,想完全掌控计算机资源当然首选C/C++,未来工业级别的软件也好,人工智能产品也好,包括目前很火的VR技术,想要流畅、实时肯定要高配计算机加上高效的代码,专注于性能的C++仍然在向未来招手。

        这篇算是Java学习的上篇,我努力争取更新出它的下篇吧。祝大家在疫情期间也能获得快乐。世界杯要来了,足球比赛看起来呀!透露一个我对今晚卡塔尔对阵厄瓜多尔的预测吧,1:1或者2:1,卡塔尔不败!

         

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas_Lbw

欣赏我就赏赐给我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值