28 Object类

Object类是层次结构最顶层的类,所有的类都以Object类作为父类

比如通过下面的例子就可以看到

public class PrintMain {
    public static void main(String[] args) {
        User user = new User("user A", 30);
        System.out.println(user);    //返回User@b4c966a
        System.out.println(user.toString());    //同样返回User@b4c966a
    }
}

我们看一下Println是怎么写的

//查看println的定义
public void println(Object x) {
        String s = String.valueOf(x);
        if (getClass() == PrintStream.class) {
            writeln(String.valueOf(s));
        } else {
            synchronized (this) {
                print(s);
                newLine();
            }
        }
    }

//查看valueOf定义
public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

//查看toString定义
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

这样就知道了为什么会输出一个class的名称加上@符号,再加上一个地址值了。

而且println传入的是一个Object类,所以我们写的User类也是默认extendsObject类的

在JDK的帮助文档里面,关于toString中提到了建议所有子类覆盖重写此方法

那么我们可以用IDEA里面的alt+insert,里面就有toString()方法来自动重写

重写之后的方法变为:

    @Override
    public String toString() {
        return "User{" +
                "userID='" + userID + '\'' +
                ", UserMoney=" + UserMoney +
                '}';
    }

此时我们再运行下结果就变为

public class PrintMain {
    public static void main(String[] args) {
        User user = new User("user A", 30);
        System.out.println(user);    //返回User{userID='user A', UserMoney=30}
    }
}

在看一下equals方法是怎么实现的

public class ObjectDemo{
    public static void main(String[] args){
        User u1 = new User("mion", 22);
        User u2 = new User("mion", 22);
        System.out.println(s1 == s2);    //false
        System.out.println(s1.equals(s2));    //false
    }
}

查看equals方法

public boolean equals(Object obj) {
        return (this == obj);
    }

再使用IDEA按模板生成重写方法

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;        //判断地址是否相同
        if (o == null || getClass() != o.getClass()) return false;
        //判断是否来自于同一个类

        User user = (User) o;    //向下转型
        
        //开始了各种比较
        if (UserMoney != user.UserMoney) return false;
        return userID != null ? userID.equals(user.userID) : user.userID == null;
    }

此时再看之前的代码

public class ObjectDemo{
    public static void main(String[] args){
        User u1 = new User("mion", 22);
        User u2 = new User("mion", 22);
        System.out.println(s1 == s2);    //false,因为还是比较地址
        System.out.println(s1.equals(s2));    //true
    }
}

29 Java包装类(包括数据类型处理)

将基本数据类型封装成为对象的好处在于可以在对象中定义更多的功能方法操作该数据

基本类型         包装类(都位于java.lang下)
byte            Byte
short           Short
int             Integer
long            Long
float           Float
double          Double
char            Character
boolean         Boolean

拿个Integer举个栗子

根据JDK文档描述,使用静态方法创建对象

public class IntegerDemo{
    public static void main(String[] args){
        Integer i1 = Integer.valueOf(100);
        System.out.println(i1);    //返回100,而不是地址值,可见它是重写了toString方法的
        
        Integer i2 = Integer.valueOf("666");
        System.out.println(i2);    //返回666,对于字符串也可以对应下来
    }
}

int 与 String之间的相互转换
public class IntegerDemo{
    public static void main(String[] args){
        //方式1,通过字符串拼接的方式生成新的字符串
        int number = 100;
        String s1 = "" + number;
        
        //方式2,使用String包装类的方法public static String valueOf(int i)
        //String valueOf的参数重载了很多类型
        String s2 = String.valueOf(number);
        
        //分割线===========
        
        //使用Integer作为桥梁将String转为int
        String s3 = "100";
        Integer i = Integer.valueOf(s);
        int x = i.intValue();
        
        //方法2,使用public static int parseInt(String s)
        int y = Integer.parseInt(s);

    }
}

案例

将一个字符串"91 27 46 38 50"进行排序,输出"27 38 46 50 91"

思路:

  • 定义一个字符串
  • 把字符串中的数字数据存储到一个int类型的数组中

    • 使用public String[] split(String regex)方法来分割
    • 再定义一个int数组,把String[]数组中的每一个元素存储到int数组中

    使用public static int parseInt(String s)方法

  • int数组进行排序
  • 把排序后的int数组中的元素进行拼接得到一个字符串,可以使用StringBuilder来实现
  • 得到结果输出
import java.util.Arrays;

public class StringAndInt {
    public static void main(String[] args) {
        String s1 = "91 27 46 38 50";
        String[] s2 = s1.split(" ");
        int[] i1 = new int[s2.length];
        for (int i = 0; i < i1.length; i++) {
            i1[i] = Integer.parseInt(s2[i]);
        }
        Arrays.sort(i1);
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < i1.length; i++) {
            sb.append(i1[i]);
            sb.append(" ");
        }
        String s3 = sb.toString();
        System.out.println(s3);


    }
}

自动装箱和拆箱

装箱:把基本数据类型转换为对应的包装类类型

拆箱:把包装类类型转换为对应的基本数据类型

Integer i = Integer.valueOf(100);    //装箱,int型100装入Integer类中
Integer ii = 100;    //自动装箱
ii = ii.intValue() + 200;    //使用intValue()方法拆箱,之后赋值给ii为自动装箱
ii += 200;    //自动拆箱装箱

在开发中,如果使用一个引用数据类型,那么操作之前最好来一个判断是否是null

30 Java日期相关

Date类

public Date():分配一个Date对象,并初始化,时间精确到毫秒
public Date(long date):分配一个Date对象,初始化从标准时间与指定时间的差值
Date类的toString方法都重写了
Date类的常用方法
public long getTime():获取从UTC到现在的毫秒值
public void setTime(long time):设置时间,参数是毫秒值

SimpleDateFormat类

SimpleDateFormat类是一个可以以区域设置敏感的方式进行格式化和解析日期

日期和时间格式由日期和时间模式字符串制定,在日期和时间模式字符串中,从A到Z以及从a到z引号的字母被解释为表示日期或时间字符串的组件的模式字母

常见的对应关系如下

y -> 年 
M -> 月 
d -> 日 
H -> 时
m -> 分
s -> 秒

其构造方法:

public SimpleDateFormat():使用默认模式和日期格式构造
public SimpleDateFormat(String pattern):使用给定模式构造

格式化与解析:

//从Date到String:
public final String format(Date date): 将Date格式化成为人能看懂的

//从String到Date:
public Date parse(String source): 从日期解析到Date对象
//格式化
public class SDFDemo{
    public static void main(String[] args){
        //格式化
        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String s = sdf.format(d);
        System.out.println(s);
        
        //解析
        String ss = "2021-01-24 22:22:22";
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date dd = sdf2.parse(ss);
        System.out.println(dd);
    }
}

Calendar类

Calendar为某一时刻和一组日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法

使用getInstance获取Calendar对象:Calendar rightNow = Calendar.getInstance();
public class CalendarDemo{
    public static void main(String[] args){
        //获取对象
        Calendar c = Calendar.getInstance();    //多态的形式
        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH)+1;    //自带的MONTH是从0开始
        int date = c.get(Calendar.DATE);
        System.out.println(year+"Nian"+month+"yue"+date+"ri");
    }
}
常用方法
public abstract void add(int field, int amount):根据field规则增减mount单位的日历字段
public final void set(int year, int month, int date):设置一个日历日期

31 Java异常

Throwable类是Java语言中所有错误和异常的超类(祖宗类),分为ErrorException

其中Error子类的问题大多都是Java处理不了的,比如硬件层面、内存资源不足等。

其中Exception又分为RuntimeException非RuntimeException

其中前者在编译期间不检查,出现问题后要回来修改代码

后者是编译器就必须处理的,否则程序就无法通过编译


JVM的默认处理方案

如果程序出现了问题,我们没有做任何的处理的话,JVM就会做默认的处理:

  • 把异常的名称,异常原因以及异常出现的位置等信息输出在控制台
  • 程序停止执行

异常处理

我们需要自己来处理程序的异常,有两种方案来处理

  • try... catch ...
  • throws
try...catch...方案

格式:

try {
    可能出现异常的代码;
} catch(异常类名 变量名){
    异常的处理代码;
}

执行流程:程序从try里面的代码开始执行,出现异常以后会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统。当Java运行时系统接收到异常对象后,回到catch中去找匹配的异常类,找到后就进行异常的处理。

执行完毕之后,程序将还可以继续往下执行,例如

public static void method{
    try{
        int[] arr = {1, 2, 3};
        System.out.println(arr[3]);    //此时new了一个ArrayIndexOutOfBoundsException
    } catch (ArrayIndexOutOfBoundsException e){
        e.printStackTrace();
    }
}
throws处理方案

并不是所有的异常都可以通过try...catch...来处理的,针对这种情况Java提供了throws处理方案

//runtime exception情况
public static void method() throws ArrayIndexOutOfBoundsException { 
        int[] arr = {1, 2, 3};
        System.out.println(arr[3]);
    }
}

//编译型异常情况
public static void method2() throws ParseException{
    String s = "2020-12-12";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date d = sdf.parse(s);
    System.out.println(d);
}

throws只是抛出,并没有进行处理,真正的处理还得让try catch来处理

我们可以先抛出异常,在调用的时候让使用者去自己处理异常


自定义异常

格式:

public class 异常类名 extends Exception {
    无参构造
    带参构造
}

例如

public class ScoreException extends Exception{
    public ScoreException(){}
    public ScoreException(String message){
        super(message);    
        //通过super关键字,message首先传到Exception类,再传到Throwable类
        //最后得到Throwable中的detailMessage变量
    }
}

public class Teacher{
    public void checkScore(int score) throws ScoreException{
        if(score<0 || score>100){
            throw new ScoreException("分数不正常");    
            //抛出一个异常对象,传给Throwable类
        }else {
            System.out.println("分属正常");
        }
    }
}

public class TeacherTest{
    publis static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入分数:");
        int score = sc.nextInt();
        
        Teacher t = new Teacher();    
        //由于Teacher类抛出了一个Exception,所以必须要进行手动处理
        try{
            t.checkScore(score);
        } catch(ScoreException e){
            e.printStackTrace();
        }
    }
}

关于throwsthrow的区别

throwsthrow
用在方法声明后面,跟的是异常类名用在方法体内,跟的是异常对象名
表示抛出异常,由该方法的调用者处理表示抛出异常,由方法体内的语句处理
表示可能出现异常,不一定会发生异常执行throw就一定抛出了某种异常

32 集合进阶体系结构

主要包括:

  • Collection
  • List
  • Set
  • 泛型
  • Map
  • Collections

集合提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变

其中Collection为单列集合,Map为双列集合

其中单列集合中的List集合可以存储可重复数据,Set集合会自动去重数据

List是一个接口,其实现类包括ArrayListLinkedList等等

Set下的实现类包括HashSetTreeSet等等

Map的实现类有HashMap等等


Collection集合

全称Interface Collection<E>,属于java.util包下

定义:public interface Collection<E> extends Iterable<E>

JDK不提供此接口的任何直接实现,要用的话要使用SetList子接口来实现

通过ArrayList来实现Collection集合的对象

Collection<String> c = new ArrayList<String>();

//添加元素:loolean add(E e)
c.add("hello");
c.add("world");

System.out.println(c);    //返回[hello, world],说明重写了toString()方法
Collection集合的常用方法
方法名说明
boolean add(E e)添加元素
boolean remove(Object o )从集合中移除指定的元素
void clear()清空集合中的元素
boolean contains(Object o )判断集合中是否存在指定的元素
boolean isEmpty()判断是否为空
int size()获取长度(集合元素个数)

Ps: IDEA中按Alt+7可以打开一个小窗口看类的所有信息

Collection集合的遍历

使用Iterator迭代器

Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到

Iterator中的常用方法:

  • E next():返回迭代中的下一个元素
  • boolean hasNext():如果迭代具有更多元素,则返回true
Collection<String> c = new ArrayList<String>();
c.add("hello");
c.add("world");
c.add("java");

Iterator<String> it = c.iterator();
System.out.println(it.next());    //hello
System.out.println(it.next());    //world
System.out.println(it.next());    //java
System.out.println(it.next());    //NoSuchElementException

集合的使用步骤汇总:1创建集合对象,2添加元素,3遍历集合(先获取迭代器对象)

33 List集合

定义:public interface List<E> extends Collection<E>

List集合的特点:

  • 有索引(有序性,当然是指存储和取出的顺序有序)
  • Set集合相比可以允许重复的元素
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
list.add("java");    //可重复

Iterator<String> it = list.iterator();
while (it.hasNext()){
    String s = it.next();
    System.out.println(s);
}
List集合特有方法
方法名说明
void add(int index, E element)根据集合的索引插入指定的元素
E remove(int index)根据索引删除元素,返回被删元素
E set(int index, E element)根据索引修改元素,返回修改前元素
E get(int index)根据索引拿元素
并发修改异常
public class ListDemo{
    public static void main(String[] args){
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");
        
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String s = it.next();    
            if(s.equals("world")){
                list.add("javaee");
                //出现ConcurrentModificationException
            }
        }
    }
}

出现的原因:使用add(E e)方法的时候,会将变量modCount的值自增,而每次使用next()方法的时候,都会判断modCount(修改)的值是否和expectedModCount(预期修改次数,初始为0)的值相等

ListIterator列表迭代器

定义:public interface ListIterator<E> extends Iterator<E>

可以实现从任一方向来遍历List,其常用方法有:

  • E next(): 返回迭代中的下一个元素
  • boolean hasNext():如果有下一个元素,就返回true
  • E previous():返回上一个元素
  • boolean hasPrevious():如果有上一个元素,则返回true
  • void add(E e):将指定的元素插入列表

通过列表迭代器实现上面那种需求,则没有并发修改异常

ListIterator<String> lit = list.listIterator();
while (lit.hasNext()){
    String s = lit.next();
    if(s.equals("world")){
        lit.add("javaee");
    }
}

因为在列表迭代器中,每进行一次add方法,都会有expectedModCount = mod Count这条语句,所以判断的时候永远都不会出错

增强for循环

Collection集合的对象都允许成为增强for循环的目标,增强for循环内部原理是一个Iterator迭代器

格式为:

for(元素数据类型 变量名:数组或者Collection集合){
    循环体;
}
范例:
int[] arr = {1,2,3,4,5}
for(int i : arr){
    System.out.println(i);
}
Java链表集合的特有功能

Java中的LinkList的存储结构是双链表

  • public void addFirst(E e):开头插入元素
  • public void addLast(E e): 列表末尾插入元素
  • public E getFirst(): 拿第一个元素
  • public E getLast():拿最后一个元素
  • public E removeFirst():删掉第一个元素O(1),返回删掉的
  • public E removeLast():删掉最后一个元素O(n),返回删掉的

34 Set集合与Hash

定义:public interface Set<E> extends Collection<E>

Set集合的特点

  • Set集合中不存在重复元素
  • 没有索引,所以不能用普通for循环遍历,迭代器和增强for都可以

1、HashSet是其一个实现类,它对集合的迭代顺序不作任何保证,例如

Set<String> set = new HashSet<String>();
set.add("hello");
set.add("world");
set.add("java");
set.add("java");    //不会包含重复元素

for(String s : set){
    System.out.println(s);    //返回world java hello,无序了
}

底层数据结构是哈希表(实际为HashMap实例)

如果我们想要使用HashSet来去重从一个类出来的而且成员变量一样的不同的对象,可以通过重写equals方法和hashCode方法来解决,重写只需要IDEA生成即可

附加:public int hashCode()可以返回对象的哈希值

字符串类重写了hashCode方法

Java里面的哈希表底层是通过顺序表(数组)+链表实现的

2、 LinkHashSet是一个由链表和哈希表实现的Set接口,与HashSet比可以保证元素存储读取有序

Set<String> set = new HashSet<String>();
set.add("hello");
set.add("world");
set.add("java");
set.add("java");    //不会包含重复元素

for(String s : set){
    System.out.println(s);    //返回hello world java,有序了
}

3、TreeSet接口是可以将里面的元素有序的一种Set接口,具体的排序方式可以有Comparator或者自然排序,选用哪一种取决于构造方法是哪一种

TreeSet<Integer> ts = new TreeSet<Integer>();
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);        //同样也是Set下的
for(Integer i : ts){
    System.out.println(i);    //10 20 30 40 50,默认按照自然排序进行排序
}

Comparable接口

这是一个自然排序接口,该接口对实现它的每个类的对象都会强加一个整体排序,我们可以通过重写compareTo(to)方法来实现我们自己想要的排序方式

Comparator接口

TreeSet集合存储自定义对象时,其带参构造方法使用的是比较器排序对元素进行排序的

比较器排序就是让集合构造方法接收Comparator的实现类对象,重写compare(to1, to2)方法

35 泛型与可变参数

泛型是JDK5中引入的特性,他提供了编译时类型安全检测机制,可以在编译时检测到非法的类型

泛型本质是参数化类型,也就是说所操作的数据类型被指定成为一个参数

泛型定义格式:

<类型>: 指定一种类型的格式,这里的类型可以看成是形参
<类型1, 类型2...>: 指定多种类型的格式,用逗号隔开,类型可看成形参
将来具体调用的时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型

使用泛型的两个好处:

  • 可以在编译期间发现可能的ClassCastException异常
  • 可以避免一些强制类型转换

泛型类

当我们希望某个方法传入的参数可以是不同类型的时候,可以使用泛型类

定义格式:

修饰符 class 类名<类型>{}

public class Generic<T>{}
此处的T可以随便写为任意表示,常见的如T,E,K,V等形式的参数常用于表示泛型
public class Generic<T>{
    private T t;
    
    public T getT(){
        return t;
    }
    public T getT(T t){
        this.t = t;
    }
}

public class GenericDemo{
    public static void main(String[] args){
        Generic<String> g1 = new Generic<String>();
        g1.setT("mion");    //自动变成String
        
        Generic<Integer> g2 = new Generic<Integer>();
        g2.setT(233);        //自动变成Integer
    }
}

泛型方法

可以重载一个方法为:

public class Generic{
    public void show(String s){
        System.out.println(s);
    }
    public void show(Integer i){
        System.out.println(i);
    }
    public void show(Boolean b){
        System.out.println(b);
    }
}

这样如果我们又要添加一个类型,就得再写一个重载,很不方便,使用泛型类就可以完美解决

public class Generic<T>{
    public void show(T t){
        System.out.println(t);
    }
}

//调用只需要尖括号里面写对应的数据类型就可以了
public class GenericDemo{
    public static void main(String[] args){
        Generic<String> g1 = new Generic<>();
        g1.show("mion");
        Generic<Integer> g2 = new Generic<>();
        g2.show("233");
    }
}

如果我们不想定义的时候传类型,只想用的时候传类型,那么就用泛型方法就行了

定义格式:

修饰符 <类型> 返回值类型 方法名(类型 方法名){}
例如 public <T> void show(T t){}

那么上面的就可以改进写法为:

public class Generic{
    public <T> void show(T t){
        System.out.println(t);
    }
}

泛型接口

定义格式:

修饰符 interface 接口名 <类型> {}
例如 public interface Generic<T>{}
public interface Generic<T>{
    void show(T t);
}

public class GenericImpl<T> implements Generic<T>{
    @Override
    public void show(T t){
        System.out.println(t);
    }
}

public class GenericDemo{
    public static void main(String[] args){
        Generic<String> g1 = new GenericImpl<>();
        g1.show("mion");
        
        Generic<Integer> g2 = new GenericImpl<>();
        g2.show("233");
    }
}

类型通配符

为了表示各种泛型List的父亲,可以使用类型通配符(?),将问好作为类型实参传递给List集合,写作List<?>(意思是元素类型未知的List),此问号的元素类型可以匹配任何类型

类型通配符<?>
类型通配符上限<? extends 类型>
类型通配符下限<? super 类型>

如何理解,看下面代码

public class GenericDemo{
    public static void main(String[] args){
        List<?> list1 = new ArrayList<Object>();
        List<?> list2 = new ArrayList<Number>();
        List<?> list3 = new ArrayList<Integer>();
        
        List<? extends Number> list4 = new ArrayList<Object>();    //报错
        //因为上限为Number
        List<? extends Number> list5 = new ArrayList<Number>();    //OK
        
        List<? super Number> list6 = new ArrayList<Number>();    //OK
        list<? super Number> list7 = new ArrayList<Integer>();    //报错
        //因为下限为Number
    }
}

可变参数

public static void main(String[] args){
    System.out.println(sum(1));                //1
    System.out.println(sum(1,2));            //3    
    System.out.println(sum(1,2,3));            //6
    System.out.println(sum(1,2,3,4));        //10
}

public static int sum(int... a){    //传入参数之后,a会变成一个int型数组,可以遍历了
    int sum = 0;
    for(int i : a){
        sum += i;
    }
    return sum
}

如果想传多个参数的话,把固定的参数写在可变参数前面就好了,这样前面的参数就会传到固定参数里面

可变方法的使用

Arrays工具类当中有一个静态方法:

public static <T> List <T> asList(T... a):返回由指定数组支持的固定大小的列表(可改不可增删)

List接口中有一个静态方法:

public static <E> List <E> of(E... element):返回包含任意数量元素的不可变列表(增删改都不行)

Set接口中有一个静态方法:

public static <E> Set <E> of (E... element):返回一个包含任意数量元素的不可变集合(不能重元素)

List<String> list = Arrays.asList("hello", "world", "java");
List<String> list2 = List.of("hello", "world", "java");
Set<String> set = Set.of("hello", "world", "java");

36 Map集合

位于java.util包下

Map集合概述:

  • Interface Map <K, V> 其中K是键的类型,V是值的类型
  • 可以将键映射到值的对象,不能包含重复的键,每个键可以映射到最多一个值
  • Map是一个接口,不能直接创建实例,有两种方式来创建

    • 多态的方式
    • 通过HashMap这种具体的实现类来创建
Map<String,String> map = new HashMap<>();
map.put("akb","东京");
map.put("ske","名古屋");
map.put("hkt","福冈");
System.out.println(map);    //返回{akb=东京, ...}
map.put("akb","秋叶原");
System.out.println(map);    //返回{akb=秋叶原, ...},键不能重复,重复就会被替换    
Map集合的基本功能
  • V put(K key,V value):添加元素
  • V remove(Object key):根据key删除元素
  • void clear():清空
  • boolean containsKey(Object key):判断是否包含指定的key
  • boolean containsValue(Object value):判断是否包含指定的value
  • boolean isEmpty():判断是否为空
  • int size():获取长度
Map集合的获取功能
  • V get(Object key):根据key获取value
  • Set<K> keySet():获取key的集合
  • Collection<V> values():获取value的集合
  • Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
Map集合的遍历

思路一:

  • 获取所有key的集合,用KeySet()实现
  • 遍历Key集合,用增强for实现
  • 根据key去找值,用get(Object key)方法实现
Set<String> KeySet = map.KeySet();
for(String key : KeySet){
    String value = map.get(key);
    System.out.println(key + "," + value);
}

思路二:

  • 获取所有键值对对象的集合,使用Set<Map.Entry<K,V>> entrySet()方法
  • 用增强for循环遍历set,得到每一个键值对对象
  • getKey()getValue()来获取键与值
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for(Map.Entry<String, String>> me : entrySet){
    String key = me.getKey();
    String value = me.getValue();
    System.out.println(key+","+value);
}

37 File类(I/O流开始)

File类属于java.io包下

构造方法:

方法名说明
File(String pathname)通过将给定的路径转换为抽象路径名来创建新的File实例
File(String parent, String child)从父路径和子路径创建新的File实例
FIle(File parent, String child)从父抽象路径和子路径创建新的File实例
File f1 = new File("C:\\AKB\\48\\Mion.txt");
System.out.println(f1);    //返回C:\AKB\48\Mion.txt,说明重写了toString方法

File f2 = new File("C:\\AKB", "48\\Mion.txt");
//输出和f1一样

File f3 = new File("C:\\AKB");
File f4 = new File(f3, "48\\Mion.txt");
创建功能
方法名说明
public boolean createNewFile()当具有该名称的文件不存在时,创建一个空间文件
public boolean mkdir()创建目录
public boolean mkdirs()创建包含了父目录在内的目录

目录或者文件存在的话,就会返回false

判断和获取功能
方法名说明
public boolean isDirectory()判断是否为目录
public boolean isFile()判断是否为文件
public boolean exitst()判断是否存在
public String getAbsolutePath()获取绝对路径
public String getPath()获取路径字符串
public String getName()获取指定路径的目录或文件的名字
public String[] list()返回路径下文件和目录的String数组
public File[] listFiles()返回路径下文件和目录的File对象数组

删除功能:public boolean delete(),如果删的是目录就要先把目录下的文件和目录都删了

深度优先获取目录下所有内容
File[] fileArray = srcFile.listFiles();
if(fileArrat != null){
    for(File file : fileArray){
        if(file.isDirectory()){
            getAllFilePath(file);    //递归
        } else {
            System.out.println(file.getAbsolutePath());
        }
    }
}

38 字节流

IO流主要分为两种

  • 字节流
  • 字符流

输入字节流的定义

public abstract class InputStream extends Object implement Closeable

InputStream是表示输入字节流的所有类的超类

输出字节流的定义

public abstract class OutputStream extends Object implement Closeable, Flushable

同样为字节输出流所有类的超类

将数据写入File的方法:FileOutputStream(String name),创建文件输出流以指定的名称写入文件

当new了一个文件输出流对象后,总共发生了3件事:

  • 调用系统功能创建了文件
  • 创建了字节输出流对象
  • 让字节输出流对象指向创建好的文件

将指定内容写入输出流的方法void write(int b)

IO操作完毕后都需要释放资源,使用close()方法

字节流写数据

字节流写数据的三种方式
方法名说明
void write(int b)一次写一个字节数据
void write(byte[] b)一次写一个字节数组的数据
void write(byte[] b, int off, int length)一次写字节数组一段的数据(按off,length索引)

FileOutputStream类中,如果传的参数路径文件不存在, 构造方法会给你自动new一个文件

FileOutputStream fos = new FileOutputStream("abcde.txt");
byte[] bys = "abcde".getBytes();
fos.write(bys);        //巧用getbytes方法来拿到字节数据写入

字节流写数据的两个小问题
  1. 字节流写数据实现换行

不同的操作系统对应的换行符不一样

Linux -> n

Mac -> r

Windows -> rn

fos.write("\r\n".getBytes());
  1. 追加写入方式

创建对象时如果传递第二个参数为true时则为追加写入模式(从末尾开始写入)

FileOutputStream fos = new FileOutputStream("abcde.txt", true);

字节流写数据加入异常处理

finally:在异常处理时可以用finally块来执行所有清除操作,比如IO流释放资源

特点:被finally控制的语句一定会执行,除非JVM退出

try{
    可能出现异常的代码;
} catch{
    异常的处理代码;
} finally{
    执行的清除操作;
}
FileOutputStream fos = null;
try{
    fos = new FileOutputSteam("abcde.txt");
    fos.write("hello".getBytes());
} catch (IOException e){
    e.printStackTrace();
} finally {
    if(fos != null){
        try{
            fos.close();
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

JDK7后的改进方案(优先)

public static void method3(){
    try(FileReader fr = new FileReader("fr.txt");
        FileWriter fw = new FileWriter("fw.txt");){
        char[] chs = new char[1024];
        int len;
        while((len = fr.read()) != -1){
            fw.write(chs, 0, len);
        }
    } catch(IOException e){
        e.printStackTrace();
    }
}
//不需要close方法,他会自己close

JDK9的方案

public static void method3() throws IOException {
    FileReader fr = new FileReader("fr.txt");
    FileWriter fw = new FileWriter("fw.txt");
    try(fr;fw){
        char[] chs = new char[1024];
        int len;
        while((len = fr.read()) != -1){
            fw.write(chs, 0, len);
        }
    } catch(IOException e){
        e.printStackTrace();
    }
}

字节流读数据

具体步骤:

  1. 创建字节输入流对象
  2. 调用字节输入流对象的读数据方法
  3. 释放资源

读数据的三种方式

方法名方法说明
int read()一次读取一个字节的数据,如果没得数据读了就返回-1
int read(byte[] b)给定一个长度,读取b数组length值的数据到b数组里面,返回读取的字节个数
int read(byte[] b, int off, int len)给定范围,从off开始读取len个字节的数据到数组

通过循环来读取全部字节:

//read()方法
int by;
while ( (by = fis.read()) != -1){    
    System.out.print((char)by);   
}
fis.close();

//read(byte[] b)方法
byte[] bys = new byte[1024];
int len;
while((len = ifs.read(bys)) != -1){
    System.out.print(new String(bys, 0, len));
}
fis.close();

字节缓冲流

使用public class BufferedOutputStream类来实现缓冲输出流,位于java.io包下,使用字节缓冲流可以减少系统调用中断次数

与其对应的也有class BufferInputStream为缓冲输入流,调用时会创建一个内部缓冲区数组,当从流中读取或者跳过字节时,内部缓冲区会一次很多个字节的来进行填充

使用缓冲流可以提高IO效率,其构造方法为

BufferedOutputStream(OutputStream out)
与
BufferedInputStream(InputStream in)

字节缓冲流仅仅提供缓冲区,真正的读写数据仍然需要依靠基本的字节流来进行操作

因为缓冲流继承了输入输出流的方法,所以可以直接通过write和read方法读写数据

//写数据
BufferedOutputStream bos = new BufferedOutputStream(new FIleOutputStream("abcde.txt"));
bos.write("hello\r\n").getBytes());
bos.close();

//读数据
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abcde.txt"));
byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1){
    System.out.print(new String(bys, 0, len));
}
四种方式实现IO所需的时间

复制一个视频所花费的时间

方式总耗时
基本字节流一次1字节64565ms
基本字节流一次1数组107ms
字节缓冲流一次1字节405ms
字节缓冲流一次1数组60ms

最快方式:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.avi"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc\\abc.avi"));

byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1){
    bos.write(bys, 0, len);
}

bos.close();
bis.close();

39 字符流与编码

对于汉字的存储,如果使用UTF-8编码,占用3个字节

如果使用GBK编码,占用2个字节

常见的编码
  • ASCII编码(American Standard Code for Information Interchange)

    • 使用7位表示一个字符,ASCII的拓展字符集使用8位表示一个字符,共256个字符,支持欧洲常用字符,包括标点、图形符号、数字等
  • GB2312

    • 当小于127时与ASCII一样,但如果两个大于127的字符连在一起时,就表示一个汉字。共包含7000多个简体汉字,以及数学符号、希腊罗马字母、日文假名等。
    • ASCII中存在的字母标点数字重新编写了两个字长的编码,也就是全角字符,127号以下的就叫半角字符。
  • GBK

    • 常用的中文编码,在GB2312的基础上拓展而来,使用了单双字节变长编码方案。共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
  • GB18030
  • 最新的中文码表,收录汉字70244个,采用多字节编码,每个字可以由1个、2个或者4个字节组成。支持中国国内少数民族的文字,也支持繁体汉字以及日韩汉字。
  • Unicode(万国码)

    • UTF-8/16/32是Unicode的三种不同存储方式
    • 最经典的就是UTF-8,它采用可变长编码的方式,采用1~4字节来表示一个字符

    其规则为对于单字节字符,第一位为0,后面的7个作为Unicode编码

    对于多字节字符,假设为N字节字符,则前N为都设为1,第N+1位设为0

    • 电子邮件、web以及其他传送文字的应用中,优先采用UTF-8,IETF要求所有互联网协议都必须UTF-8编码
    • UTF-8编码规则

      • 128个US-ASCII字符,使用一个字节编码
      • 拉丁文等使用两个字节编码
      • 大部分常用字(包括中文),使用三字节编码
      • 其他极少使用的Unicode辅助字符,使用四字节编码

字符流的编解码问题

使用两座桥梁来实现字符流的编解码

InputStreamReader:读取字节,并且使用指定的编码将其解码为字符

InputStreamWriter:用指定的编码将传入的字符流转换为字节流输出

//字符流写入
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("abc.txt"), "UTF-8");
osw.write("秋叶原唐吉坷德");
osw.close();

//读取
InputStreamReader isr = new InputStreamReader(new FileInputStream("abc.txt"));
int ch;
while((ch = isr.read()) != -1){
    System.out.print((char)ch);
}

字符流写数据的5种方式
方法名说明
void write(int c)写一个字符
void write(char[] arr)写一个字符数组
void write(char[] arr, int off, int len)写字符数组的一部分
void write(String str)写一个字符串
void write(String str, int off, int len)写字符串的一部分

字节流写完之后要使用flush()方法进行刷新,执行close()方法的时候也会先自己先刷新再释放

字符流读数据的2种方式
方法名说明
int read()一次读一个数据
int read(char[] arr)一次读一个字符数组数据

字符流读写的简化版本

使用FileWriter类与FileReader类,这两个类分别继承了OutputStreamWriterInputStreamReader这两个类,使用这两个类的话默认给定了字符编码和适当的字节缓冲区大小。

FileReader fr = new FileReader("abc.java");
FileWriter fw = new FileWriter("efg.java");
FileWriter fw2 = new FileWriter("zxc.java");
int ch;
while( (ch=fr.read() ) != -1){
    fw.write(ch);
}

char[] chs = new char[1024];
int len;
while( (len=fr.read(chs)) != -1 ){
    fw2.write(chs, 0, len);
}
fr.close();
fw.close();
fw2.close();

字符缓冲流(自定义)

构造方法:

  • BufferedWriter(Writer out):可以传FileWriter或者OutputStreamWriter
  • BufferedReader(Reader in):可以传FileReader或者InputStreamReader
  • 传入的参数可以多一个int参数,为缓冲区大小
BufferedReader br = new BufferedReader(new FileReader("abc.java"));
BufferedWriter bw = new BufferedWriter(new FileWriter("copy.java"));

char[] chs = new char[1024];
int len;
while( (len=br.read(chs)) != -1 ){
    bw.write(chs, 0, len);
}

bw.close();
br.close();

字符缓冲流的特有功能:

  • BufferedWriter

    • void newLine():写一个行分隔符,根据不同的系统不一样
  • BufferedReader

    • public String readLine():读一行文字,不包含换行符等,如果到结尾则为null
//用bufferwriter写数据的三个步骤
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
for (int i = 0; i < 10; i++){
    bw.write("hello"+i);
    bw.newLine();
    bw.flush();
}
bw.close();

//按行读数据
BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
String line;
while((line=br.readLine()) != null){
    System.out.println(line);    //readline就要用println了
}
br.close();
IO流小结

40 特殊操作流

1.标准输入输出流

System类中有两个静态的成员变量:

public static final InputStream in:标准输入流

public static final OutputStream out:标准输出流


标准输入流

使用标准输入流来实现逐行读取字符串与整数

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//字符串
String line = br.readLine();
System.out.println(line);

//整数
int i = Integer.parseInt(br.readLine());
System.out.println(i);

Scanner类就是Java基于标准输入流来提供给我们使用的

标准输出流

System.out的本质是一个字节输出流

PrintStream ps = System.out;

字节打印流

PrintStream(String fileName):使用指定的文件名创建新的打印流

打印流的特点:

  • 只负责输出数据,不负责读取数据
  • 有自己的特有方法
PrintStream ps = new PrintStream("myOtherStream\\ps.txt");
ps.write(97);        //使用字节输出流的方法写数据,得到a
ps.print(97);        //使用自己特有方法写数据,得到97
ps.println(98);        //得到98并换行
ps.close();
字符打印流

字符打印流的构造方法

  • PrintWriter(String fileName):使用指定的文件名创建一个新的PrintWriter,不需要执行自动刷新
  • PrintWriter(Writer out, boolean autoFlush):创建一个新的PrintWriter,其中参数out为字符输出流,boolean参数如果为true,则println,printformat方法将刷新输出缓冲区

字符打印流需要flush方法才能写入文件

使用字符打印流来复制文件

BufferedReader br = new BufferedReader(new FileReader("abc.java"));
PrintWriter pw = new PrintWriter(new FileWriter("efg.java"));
String line;
while ((line = br.read()) != -1){
    pw.println(line);
}
pw.close();
br.close();

对象序列化流

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。

使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。

对象序列化流:ObjectOutputStream类,继承了OutputStream

将Java对象的原始数据类型和图形写入OutputStream,可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象

构造方法与序列化对象的方法:

ObjextOutputStream(OutputStream out):创建一个写入指定的对象序列化流实例

void writeObject(Object obj):将指定的对象写入ObjectOutputStream

反序列化对象流的方法

ObjectInputStream(InputStream in):构造方法

Object readObject():读取一个序列化流对象

关于Serializable接口:如果某个类implements了这个接口,那么这个类才能实现序列化和反序列化,这个接口不需要重写任何方法,是一个标记接口

对象序列化流演示:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("abc.txt"));
Student s = new Student("mion", 22);    //class Student implements Serializable
oos.wirteObject(s);
oos.close();

反序列化流演示:

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("abc.txt"));
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
ois.close();

对象序列化ID

序列化类都会有一个serialVersionUID,当修改了类中的方法之后,ID也会改变。可以通过声明一个serialVerisonUID字段来显式的表明自己的ID,要求是static,final,long类型。Java强烈建议我们对于每一个UID进行显式的声明,并且使用private修饰符

private static final long serialVerisonUID = 123456L;
transient关键字
private transient int age;

当成员变量被transient关键字修饰了之后,就不会参与对象序列化。序列化之后该成员变量的值为默认值。


Properties类

Properties 继承于 Hashtable。表示一个持久的属性集.属性列表中每个键及其对应值都是一个字符串。

Properties 类被许多 Java 类使用。例如,在获取环境变量时它就作为 System.getProperties() 方法的返回值。

Properties可以保存在流中或在流中加载。

Properties prop = new Properties();        //不能像Map类那样写泛型
prop.put("mion","22");
Set<Object> keySet = prop.keySet();
for(Object key : keySet){
    System.out.println(prop.get(key));
}

Properties作为集合的特有方法

方法名说明
Object setProperty(String key, String value)设置集合的键与值,底层调用Hashtable的put方法
String getProperty(String key)按键搜索属性
Set<String> stringPropertyNames()返回一个不可修改的key Set,key对应的值是String
//小插话,如何让原本接收Object参数类型的方法变成只能接收String类型的呢(或更小范围的)
//以setProperty()方法的定义为例
Object setProperty(String key, String value){
    return put(key, value);
}
Object put(Object key, Object value){
    return map.put(key, value);
}

采用特有的方法来遍历

Properties prop = new Properties();
prop.setProperty("mion", "22");
prop.setProperty("yuihan", "28");
prop.setProperty("yuiyui", "19");

Set<String> names = prop.stringPropertyNames();
for(String key:names){
    String value = prop.getProperty(key);
    System.out.println(key + value);
}

Properties和IO流相结合的方法

方法名说明
void load(InputStream inStream)从输入字节流读取集合
void load(Reader reader)从输入字符流读取集合
void store(OutputStrea out, String comments)将集合写入Properties表中,并且输出到文件
void store(Writer writer, String comments)将集合写入Properties表中,并且输出到文件
public class PropertiesDemo{
    public static void main(String[] args){
        myStore();
        myLoad();
    }
    
    public static void myLoad() throws IOException{
        Properties prop = new Properties();
        FileReader fr = new FileReader("abc.txt");
        prop.load(fr);
        fr.close();
        System.out.println(prop);
    }
    
    public static void myStore() throws IOException{
        Properties prop = new Properties();
        prop.setProperty("mion","akb");
        prop.setProperty("maomao","tsh");
        FileWriter fw = new FileWriter("abc.txt");
        prop.store(fw, null);
        fw.close();
    }
}
Last modification:May 11th, 2021 at 09:46 pm