JavaSE笔记--韩顺平(入门详细版)(三)

简介: JavaSE笔记--韩顺平(入门详细版)(三)

12.3.3  LinkedHashMap


12.3.4. TreeMap



底层机制和源码剖析:

  1. 若按照compare方法比较key相同则无法加入value值
public class TreeMap_ {
    public static void main(String[] args) {
        //使用默认的构造器,创建TreeMap, 是字母排序
        /*
            要求:按照传入的 k(String) 的大小进行排序
         */
//        TreeMap treeMap = new TreeMap();
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //按照传入的 k(String) 的大小进行排序
                //按照K(String) 的长度大小排序
                //return ((String) o2).compareTo((String) o1);
                return ((String) o2).length() - ((String) o1).length();
            }
        });
        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("kristina", "克瑞斯提诺");
        treeMap.put("smith", "斯密斯");
        treeMap.put("hsp", "韩顺平");//加入不了
        System.out.println("treemap=" + treeMap);
        /*
            解读源码:
            1. 构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator
             public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
            2. 调用put方法
            2.1 第一次添加, 把k-v 封装到 Entry对象,放入root
            Entry<K,V> t = root;
            if (t == null) {
                compare(key, key); // type (and possibly null) check
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            2.2 以后添加
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do { //遍历所有的key , 给当前key找到适当位置
                    parent = t;
                    cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else  //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加
                        return t.setValue(value);
                } while (t != null);
            }
         */
    }
}


12.3.5. Properties



注意事项和细节:

  1. Properties类继承Hashtable类,实现了Map接口,也是使用一种简直对的形式保存数据
  2. 使用特点和Hashtable类似
  3. Properties 还可以用于 从xxx.properties 文件中,加载数据到Properties类对象井进行读取和修改
  4. xxx.properties 文件通常作为配置文件
public class Properties_ {
    public static void main(String[] args) {
        //1. Properties 继承  Hashtable
        //2. 可以通过 k-v 存放数据,当然key 和 value 不能为 null
        //增加
        Properties properties = new Properties();
        //properties.put(null, "abc");//抛出 空指针异常
        //properties.put("abc", null); //抛出 空指针异常
        properties.put("john", 100);//k-v
        properties.put("lucy", 100);
        properties.put("lic", 100);
        properties.put("lic", 88);//如果有相同的key , value被替换
        System.out.println("properties=" + properties);
        //通过k 获取对应值
        System.out.println(properties.get("lic"));//88
        //删除
        properties.remove("lic");
        System.out.println("properties=" + properties);
        //修改
        properties.put("john", "约翰");
        System.out.println("properties=" + properties);
    }
}


12.4  Collections



基本介绍:

  • Collections 是一个操作 Set.List 和 Map 等集合的工具类
  • Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

常用方法:

  • 排序操作:
  • reverse (List):反转List中元素顺序
  • shuffle(List):对 List 集合元素进行随机排序
  • sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
  • sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
  • swap (List, int,int):将指定 list 集合中的 i处元素和j处元素进行交换
  • 查找替换:
  • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
  • Object max(Collection, Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
  • Object min(Collection)
  • Object min(Collection, Comparator)
  • int frequency(Collection, Object):返回指定集合中指定元素的出现次数
  • void copy(List dest, List src):将src中的内容复制到dest中
  • boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换 List 对象的所有旧值
public class Collections_ {
    public static void main(String[] args) {
        //创建ArrayList 集合,用于测试.
        List list = new ArrayList();
        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");
        list.add("tom");
//        reverse(List):反转 List 中元素的顺序
        Collections.reverse(list);
        System.out.println("list=" + list);
//        shuffle(List):对 List 集合元素进行随机排序
//        for (int i = 0; i < 5; i++) {
//            Collections.shuffle(list);
//            System.out.println("list=" + list);
//        }
//        sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
        Collections.sort(list);
        System.out.println("自然排序后");
        System.out.println("list=" + list);
//        sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
        //我们希望按照 字符串的长度大小排序
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //可以加入校验代码.
                return ((String) o2).length() - ((String) o1).length();
            }
        });
        System.out.println("字符串长度大小排序=" + list);
//        swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
        //比如
        Collections.swap(list, 0, 1);
        System.out.println("交换后的情况");
        System.out.println("list=" + list);
        //Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
        System.out.println("自然顺序最大元素=" + Collections.max(list));
        //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
        //比如,我们要返回长度最大的元素
        Object maxObject = Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();
            }
        });
        System.out.println("长度最大的元素=" + maxObject);
        //Object min(Collection)
        //Object min(Collection,Comparator)
        //上面的两个方法,参考max即可
        //int frequency(Collection,Object):返回指定集合中指定元素的出现次数
        System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));
        //void copy(List dest,List src):将src中的内容复制到dest中
        ArrayList dest = new ArrayList();
        //为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
        for(int i = 0; i < list.size(); i++) {
            dest.add("");
        }
        //拷贝
        Collections.copy(dest, list);
        System.out.println("dest=" + dest);
        //boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
        //如果list中,有tom 就替换成 汤姆
        Collections.replaceAll(list, "tom", "汤姆");
        System.out.println("list替换后=" + list);
    }
}


12.5 总结⭐️⭐️



选择集合:

  • 先判断存储的类型(一组对象[单列]或一组键值对[双列])
  • 一组对象[单列]: Collection接口
  • 允许重复:List
    增删多:LinkedList  [底层维护双向链表]
    改查多:ArrayList  [底层維护 Object类型的可变数组]
  • 不允许重复:Set
    无序:HashSet  [底层是HashMap,维护了一个哈希表,即(数组+链表+红黑树)]
    排序:Treeset  []
    插入和取出顺序一致:LinkedHashSet  [底层维护数组+双向链表]
  • 一组键[值对双列]:Map
  • 键无序:HashMap  [底层是:哈希表  jdk7:数组+链表,jdk8:数组+链表+红黑树]
  • 键排序:TreeMap  []
  • 键插入和取出顺序一致:LinkedHashMap
  • 读取文件 Propertie


13 泛型


13.1泛型



基本介绍:

  • 泛型又称参数化类型(接收数据类型的数据类型),是Jdk5.0出现的新特性,解决数据类型的安全性问题
  • 在类声明或实例化时只要指定好需要的具体的类型即可
  • Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮

基本语法:

interface 接口 <T>{}
class 类 <K,V>{}
在类名后指定类型参数的值(类型):
  List<String> strList = new ArrayList<String>();
  lterator<Customer> iterator = customers.iterator();



注意事项和细节:

  1. 泛型只能是引用类型
  2. 在给泛型指定具体类型后,可以传入该类型或者其子类类型
  3. List list3 = new ArrayList():默认给它的 泛型是[<E> E就是 Object]

例:

public class GenericExercise02 {
    public static void main(String[] args) {
        ArrayList<Employee> employees = new ArrayList<Employee>();
        employees.add(new Employee("jack", 2000, new MyDate(1, 1, 1)));
        employees.add(new Employee("tom", 2000, new MyDate(6, 1, 1)));
        employees.add(new Employee("tom", 3000, new MyDate(3, 1, 1)));
        employees.add(new Employee("jack", 2000, new MyDate(1, 1, 3)));
        employees.sort(new Comparator<Employee>() {//传入匿名内部类接口重写compare方法
            @Override
            public int compare(Employee o1, Employee o2) {
                if (!(o1 instanceof Employee) && (o2 instanceof Employee)) {
                    System.out.println("类型不正确");
                    return 0;
                }
                return o1.getBirthday().compareTo(o2.getBirthday());//在MyDate内重写compareTo方法
            }
        });
        for (Employee employee : employees) {
            System.out.println(employee);
        }
    }
}
class Employee {
    private String name;
    private int sal;
    private MyDate birthday;
    public Employee(String name, int sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getSal() {
        return sal;
    }
    public void setSal(int sal) {
        this.sal = sal;
    }
    public MyDate getBirthday() {
        return birthday;
    }
    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }
    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }
}
class MyDate implements Comparable<MyDate> {
    private int year;
    private int month;
    private int day;
    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }
    public int getMonth() {
        return month;
    }
    public void setMonth(int month) {
        this.month = month;
    }
    public int getDay() {
        return day;
    }
    public void setDay(int day) {
        this.day = day;
    }
    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
    @Override
    public int compareTo(MyDate o) {
        int yearMinus = year - o.year;
        if (yearMinus != 0) {
            return yearMinus;
        }
        int monthMinus = month - o.month;
        if (monthMinus != 0) {
            return monthMinus;
        }
        return day - o.day;
    }
}


13.2 自定义泛型


13.2.1泛型类



基本语法:

class 类名<T,R...>{

 成员;

}



注意事项和细节:

  1. 普通成员可以使用泛型(属性、方法)
  2. 使用泛型的数组,不能初始化
  3. 静态方法中不能使用类的泛型
  4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
  5. 如果在创建对象时,没有指定类型,默认为Object
public class CustomGeneric_ {
    public static void main(String[] args) {
        //T=Double, R=String, M=Integer
        Tiger<Double,String,Integer> g = new Tiger<>("john");
        g.setT(10.9); //OK
        //g.setT("yy"); //错误,类型不对
        System.out.println(g);
        Tiger g2 = new Tiger("john~~");//OK T=Object R=Object M=Object
        g2.setT("yy"); //OK ,因为 T=Object "yy"=String 是Object子类
        System.out.println("g2=" + g2);
    }
}
//1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
//2, T, R, M 泛型的标识符, 一般是单个大写字母
//3. 泛型标识符可以有多个.
//4. 普通成员可以使用泛型 (属性、方法)
//5. 使用泛型的数组,不能初始化
//6. 静态方法中不能使用类的泛型
class Tiger<T, R, M> {
    String name;
    R r; //属性使用到泛型
    M m;
    T t;
    //因为数组在new 不能确定T的类型,就无法在内存开空间
    T[] ts;
    public Tiger(String name) {
        this.name = name;
    }
    public Tiger(R r, M m, T t) {//构造器使用泛型
        this.r = r;
        this.m = m;
        this.t = t;
    }
    public Tiger(String name, R r, M m, T t) {//构造器使用泛型
        this.name = name;
        this.r = r;
        this.m = m;
        this.t = t;
    }
    //因为静态是和类相关的,在类加载时,对象还没有创建
    //所以,如果静态方法和静态属性使用了泛型,JVM就无法完成初始化
//    static R r2;
//    public static void m1(M m) {
//
//    }
    //方法使用泛型
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public R getR() {
        return r;
    }
    public void setR(R r) {//方法使用到泛型
        this.r = r;
    }
    public M getM() {//返回类型可以使用泛型.
        return m;
    }
    public void setM(M m) {
        this.m = m;
    }
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
    @Override
    public String toString() {
        return "Tiger{" +
                "name='" + name + '\'' +
                ", r=" + r +
                ", m=" + m +
                ", t=" + t +
                ", ts=" + Arrays.toString(ts) +
                '}';
    }
}


13.2.2泛型接口



基本语法:

interface 接口名<T,R...>{}



注意事项和细节:

  1. 静态成员不能使用泛型
  2. 泛型接口的类型,在继承接口或者实现接口时确定
  3. 没有指定类型,默认为Object
public class CustomInterfaceGeneric {
    public static void main(String[] args) {
    }
}
/**
 *  泛型接口使用的说明
 *  1. 接口中,静态成员也不能使用泛型
 *  2. 泛型接口的类型, 在继承接口或者实现接口时确定
 *  3. 没有指定类型,默认为Object
 */
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {
}
//当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double
//,在实现IUsu接口的方法时,使用String替换U, 是Double替换R
class AA implements IA {
    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double aDouble) {
    }
    @Override
    public void run(Double r1, Double r2, String u1, String u2) {
    }
}
//实现接口时,直接指定泛型接口的类型
//给U 指定Integer 给 R 指定了 Float
//所以,当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float> {
    @Override
    public Float get(Integer integer) {
        return null;
    }
    @Override
    public void hi(Float aFloat) {
    }
    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {
    }
}
//没有指定类型,默认为Object
//建议直接写成 IUsb<Object,Object>
class CC implements IUsb { //等价 class CC implements IUsb<Object,Object> {
    @Override
    public Object get(Object o) {
        return null;
    }
    @Override
    public void hi(Object o) {
    }
    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {
    }
}
interface IUsb<U, R> {
    int n = 10;
    //U name; 不能这样使用
    //普通方法中,可以使用接口泛型
    R get(U u);
    void hi(R r);
    void run(R r1, R r2, U u1, U u2);
    //在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
    default R method(U u) {
        return null;
    }
}


13.2.3泛型方法



基本语法:

修饰符<T,R...>返回类型 方法名(参数列表){}



注意事项和细节:

  1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中
  2. 当泛型方法被调用时,类型会确定
  3. public void eat(Ee) {},
    修饰符后没有<T,R.> eat
    方法不是泛型方法,而是使用了泛型
public class CustomMethodGeneric {
    public static void main(String[] args) {
        Car car = new Car();
        car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型
        System.out.println("=======");
        car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型
        //测试
        //T->String, R-> ArrayList
        Fish<String, ArrayList> fish = new Fish<>();
        fish.hello(new ArrayList(), 11.3f);
    }
}
//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类
    public void run() {//普通方法
    }
    //说明 泛型方法
    //1. <T,R> 就是泛型
    //2. 是提供给 fly使用的
    public <T, R> void fly(T t, R r) {//泛型方法
        System.out.println(t.getClass());//String
        System.out.println(r.getClass());//Integer
    }
}
class Fish<T, R> {//泛型类
    public void run() {//普通方法
    }
    public<U,M> void eat(U u, M m) {//泛型方法
    }
    //说明
    //1. 下面hi方法不是泛型方法
    //2. 是hi方法使用了类声明的 泛型
    public void hi(T t) {
    }
    //泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
    public<K> void hello(R r, K k) {
        System.out.println(r.getClass());//ArrayList
        System.out.println(k.getClass());//Float
    }
}

13.3 泛型继承和通配符



注意事项和细节:

  1. 泛型不具备继承性
  2. :支持任意泛型类型
  3. :支持A类以及A类的子类,规定了泛型的上限
  4. :支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
public class GenericExtends {
    public static void main(String[] args) {
        Object o = new String("xx");
        //泛型没有继承性
        //List<Object> list = new ArrayList<String>();
        //举例说明下面三个方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();
        //如果是 List<?> c ,可以接受任意的泛型类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);
        //List<? extends AA> c: 表示 上限,可以接受 AA或者AA子类
//        printCollection2(list1);//×
//        printCollection2(list2);//×
        printCollection2(list3);//√
        printCollection2(list4);//√
        printCollection2(list5);//√
        //List<? super AA> c: 支持AA类以及AA类的父类,不限于直接父类
        printCollection3(list1);//√
        //printCollection3(list2);//×
        printCollection3(list3);//√
        //printCollection3(list4);//×
        //printCollection3(list5);//×
        //冒泡排序
        //插入排序
        //....
    }
    // ? extends AA 表示 上限,可以接受 AA或者AA子类
    public static void printCollection2(List<? extends AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
    //说明: List<?> 表示 任意的泛型类型都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) { // 通配符,取出时,就是Object
            System.out.println(object);
        }
    }
    // ? super 子类类名AA:支持AA类以及AA类的父类,不限于直接父类,
    //规定了泛型的下限
    public static void printCollection3(List<? super AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
}
class AA {
}
class BB extends AA {
}
class CC extends BB {
}

例:

public class HomeWork01 {
    public static void main(String[] args) {
    }
    @Test
    public void testList() {
         DAO<User> dao = new DAO<User>();
         dao.save("001",new User(1,10,"jack"));
         dao.save("002",new User(2,11,"tom"));
         dao.save("003",new User(3,12,"mike"));
        List<User> list = dao.list();
        System.out.println(list);
        dao.update("002",new User(2,14,"faker"));
        List<User> list1 = dao.list();
        System.out.println(list1);
        dao.delete("001");
        List<User> list2 = dao.list();
        System.out.println(list2);
    }
}


14  GUI


14.1绘图



绘图原理:

  • Component类提供了两个和绘图相关最重要的方法:
  1. paint(Graphics g)绘制组件的外观
  2. repaint()刷新组件的外观
  • 当组件第一次在屏幕显示的时候,程序会自动的调用paint0方法来绘制组件,在以下情况paintQ将会被调用:
  1. 窗口最小化,再最大化
  2. 窗口的大小发生变化
  3. repaint函数被调用

Graphics类:

  • 画直线 drawLine(int x1,int yl,int x2,int y2)
  • 画矩形边框 drawRect(int x, int y, int width, int height)
  • 画椭圆边框 drawOval(int x, int y, int width, int height)
  • 填充矩形fillRect(int x, int y, int width, int height)
  • 填充椭园 fillOval(int x, int y. int width, int height)
  • 画图片 drawlmage(Image img, int x, int y, ...)
  • 画字符串 drawString(String str, int x, inty)
  • 设置画笔的字体 setFont (Font font)
  • 设置画笔的颜色 setColor(Color c)


14.2 事件控制



  • java事件处理是采取“委派事件模型”。当事件发生时,产生事件的对象,会把此"信息"传递给"事件的监听者” 处理,这里所说的"信息"实际上就是 java.awt.event 事件类库里某个类所创建的对象,把它称为”事件的对象“
    网络异常,图片无法展示
    |

基本介绍:


深入理解:

  1. 事件源:事件源是一个产生事件的对象,比如按钮,窗口等
  2. 事件:事件就是承载事件源状态改变时的对象,比如当键盘事件、鼠标事件、窗口事件等等,会生成一个事件对象,该对家保存着当前事件很多信息,比如KeyEvent 对象有含有被按下键的Gode值。java.awt.event包 和javax.swing.event包中定义了各种事
    件类型
  3. 事件类型:
    网络异常,图片无法展示
    |
  4. 事件监听噐号接口:
    (1)当事件源产生一个事件,可以传送给事件监听者处理Interfaces
    (2) 事件监听者本质是一个类,该类实现了某个事件监听器接口比如前面我们案例中的MyPanle就是一个类,它实现了
    KeyListener接口,它就可以作为一个事件监听者,对接受到的事件进行处理
    (3)事件监听器接口有多种,不同的事件监听器接口可以监听不同的事件 一个类可以实现多个监听接口
    (4)这些接口在java.awt.event包和javax.swing.event包中定义,列出常用的事件监听器接口,查看jdk 文档聚集了


15 线程(基础)



基本介绍:

  • 线程由进程创建的,是进程的一个实体
  • 一个进程可以拥有多个线程
  • 单线程:同一个时刻,只允许执行一个线程
  • 多线程:同一个时刻,可以执行多个线程
  • 并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉
  • 并行:同一个时刻,多个任务同时执行,多核cpu可以实现并行


16.1 线程使用


16.1.1 继承Thread


public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //创建Cat对象,可以当做线程使用
        Cat cat = new Cat();
        //源码
        /*
            (1)
            public synchronized void start() {
                start0();
            }
            (2)
            //start0() 是本地方法,是JVM调用, 底层是c/c++实现
            //真正实现多线程的效果,是start0(), 而不是 run
            private native void start0();
         */
        cat.start();//启动线程-> 最终会执行cat的run方法
        //cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
        //说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        //这时 主线程和子线程是交替执行..
        System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字main
        for(int i = 0; i < 60; i++) {
            System.out.println("主线程 i=" + i);
            //让主线程休眠
            Thread.sleep(1000);
        }
    }
}
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
/*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */
class Cat extends Thread {
    int times = 0;
    @Override
    public void run() {//重写run方法,写上自己的业务逻辑
        while (true) {
            //该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪”
            System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
            //让该线程休眠1秒 ctrl+alt+t
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 80) {
                break;//当times 到80, 退出while, 这时线程也就退出..
            }
        }
    }
}
16.1.2 实现Runnable
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog.start(); 这里不能调用start
        //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();
//        Tiger tiger = new Tiger();//实现了 Runnable
//        ThreadProxy threadProxy = new ThreadProxy(tiger);
//        threadProxy.start();
    }
}
class Animal {
}
class Tiger extends Animal implements Runnable {
    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}
//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
    private Runnable target = null;//属性,类型是 Runnable
    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定(运行类型Tiger)
        }
    }
    public ThreadProxy(Runnable target) {
        this.target = target;
    }
    public void start() {
        start0();//这个方法时真正实现多线程方法
    }
    public void start0() {
        run();
    }
}
class Dog implements Runnable { //通过实现Runnable接口,开发线程
    int count = 0;
    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}



对比:

  1. java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jidk帮助文档我们可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制

线程退出:

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();
        //如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
        //让t1 退出run方法,从而终止 t1线程 -> 通知方式
        //让主线程休眠 10 秒,再通知 t1线程退出
        System.out.println("main线程休眠10s...");
        Thread.sleep(10 * 1000);
        t1.setLoop(false);
    }
}
class T extends Thread {
    private int count = 0;
    //设置一个控制变量
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);// 让当前线程休眠50ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中...." + (++count));
        }
    }
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}
代理模式模拟:
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog.start(); 这里不能调用start
        //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();
//        Tiger tiger = new Tiger();//实现了 Runnable
//        ThreadProxy threadProxy = new ThreadProxy(tiger);
//        threadProxy.start();
    }
}
class Animal {
}
class Tiger extends Animal implements Runnable {
    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}
//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
    private Runnable target = null;//属性,类型是 Runnable
    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定(运行类型Tiger)
        }
    }
    public ThreadProxy(Runnable target) {
        this.target = target;
    }
    public void start() {
        start0();//这个方法时真正实现多线程方法
    }
    public void start0() {
        run();
    }
}
class Dog implements Runnable { //通过实现Runnable接口,开发线程
    int count = 0;
    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}


16.2 线程方法



常用方法1:

  • setName:设置线程名称,使之与参数 name 相同
  • getName:返回该线程的名称
  • start:使该线程开始执行;Java 虚拟机底层调用该线程的 start0方法
  • run :调用线程对象 run 方法
  • setPriority:更改线程的优先级
  • getPriority:获取线程的优先级
  • sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  • interrupt:中断线程
public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        //测试相关的方法
        T t = new T();
        t.setName("老韩");
        t.setPriority(Thread.MIN_PRIORITY);//1
        t.start();//启动子线程
        //主线程打印5 hi ,然后我就中断 子线程的休眠
        for(int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + i);
        }
        System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
        t.interrupt();//当执行到这里,就会中断 t线程的休眠.
    }
}
class T extends Thread { //自定义的线程类
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                //Thread.currentThread().getName() 获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + "  吃包子~~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
                Thread.sleep(20000);//20秒
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
                //InterruptedException 是捕获到一个中断异常.
                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}

注意事项和细节:

  1. start:底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程
  2. interrupt:中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠线程
  3. sleep:线程的静态方法,使当前线程休眠

常用方法2:

  • yield:线程的礼让。让出Cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  • join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任
public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();
        for(int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程(小弟) 吃了 " + i  + " 包子");
            if(i == 5) {
                System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
                //join, 线程插队
                //t2.join();// 这里相当于让t2 线程先执行完毕
                Thread.yield();//礼让,不一定成功..
                System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");
            }
        }
    }
}
class T2 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);//休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大) 吃了 " + i +  " 包子");
        }
    }
}



用户线程和守护线程:

  • 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  • 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
    setDaemon(True)
  • 常见的守护线程:垃圾回收机制
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //如果我们希望当main线程结束后,子线程自动结束
        //只需将子线程设为守护线程即可
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();
        for( int i = 1; i <= 10; i++) {//main线程
            System.out.println("辛苦的工作...");
            Thread.sleep(1000);
        }
    }
}
class MyDaemonThread extends Thread {
    public void run() {
        for (; ; ) {//无限循环
            try {
                Thread.sleep(1000);//休眠1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("快乐聊天,哈哈哈~~~");
        }
    }
}



16.3 线程生命周期



  • 网络异常,图片无法展示
    |

周期图:


16.4  Synchronized⭐️



  • 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

线程同步机制:


基本语法:

//同步代码块
synchronized(对象) { // 得到对象的锁,才能操作同步代码
  需要被同步代码;
}
//同步方法
public synchronized void m(String name){
  需要被同步代码;
}

例:

public class SellTicket {
    public static void main(String[] args) {
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口
    }
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;//控制run方法变量
    Object object = new Object();
    //同步方法(静态的)的锁为当前类本身
    //老韩解读
    //1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
    //2. 如果在静态方法中,实现一个同步代码块.
    /*
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
     */
    public synchronized static void m1() {
    }
    public static  void m2() {
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
    }
    //老韩说明
    //1. public synchronized void sell() {} 就是一个同步方法
    //2. 这时锁在 this对象
    //3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
    public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
        synchronized (/*this*/ object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }
            //休眠50毫秒, 模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + " 剩余票数=" + (--ticketNum));//1 - 0 - -1  - -2
        }
    }
    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一共同步方法
        }
    }
}



16.5 互斥锁⭐️



基本介绍:

  • Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  • 每个对象都对应于一个可称为“互斥锁〞 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
  • 关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问
  • 同步的局限性:导致程序的执行效率要降低
  • 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
  • 同步方法(静态的)的锁为当前类本身

注意事项和细节:

  1. 同步方法如果没有使用static修饰:默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class
  3. 实现的落地步骤:
  1. 需要先分析上锁的代码
  2. 选择同步代码块或同步方法
  3. 要求多个线程的锁对象为同一个即可
public class DeadLock_ {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();
    }
}
//线程
class DeadLockDemo extends Thread {
    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
    static Object o2 = new Object();
    boolean flag;
    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }
    @Override
    public void run() {
        //下面业务逻辑的分析
        //1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
        //2. 如果线程A 得不到 o2 对象锁,就会Blocked
        //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
        //4. 如果线程B 得不到 o1 对象锁,就会Blocked
        if (flag) {
            synchronized (o1) {//对象互斥锁, 下面就是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入1");
                synchronized (o2) { // 这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入3");
                synchronized (o1) { // 这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入4");
                }
            }
        }
    }
}


16.6 死锁



  • 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生

基本介绍:


释放锁:

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait0方法,当前线程暂停,井释放锁

不释放锁:

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
  • 线程执行同步代码块时,其他线程调用了该线程的suspendO方法将该线程挂起


16  IO流


16.0 文件



  • 文件是保存数据的地方

基本介绍:


  • 输入流:数据从数据源(文件)到程序(内存)的路径
    输出流:数据从程序(内存)到数据源(文件)的路径
    网络异常,图片无法展示
    |

文件流:


创建文件:

  • new File(String pathname) //根据文件路径构建
  • new File(File parent,String child) //根据父目录文件+子路径构建
  • new File(String parent,String child) //根据父目录+子路径构建
public class FileCreate {
    public static void main(String[] args) {
    }
    //方式1 new File(String pathname)
    @Test
    public void create01() {
        String filePath = "/Users/henghengzhu/Downloads/代码/news1.txt";
        File file = new File(filePath);
        try {
            file.createNewFile();
            System.out.println("文件创建成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //方式2 new File(File parent,String child) //根据父目录文件+子路径构建
    @Test
    public  void create02() {
        File parentFile = new File("/Users/henghengzhu/Downloads/代码");
        String fileName = "news2.txt";
        //这里的file对象,在java程序中,只是一个对象
        //只有执行了createNewFile 方法,才会真正的,在磁盘创建该文件
        File file = new File(parentFile, fileName);
        try {
            file.createNewFile();
            System.out.println("创建成功~");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //方式3 new File(String parent,String child) //根据父目录+子路径构建
    @Test
    public void create03() {
        //String parentPath = "e:\\";
        String parentPath = "/Users/henghengzhu/Downloads/";
        String fileName = "news3.txt";
        File file = new File(parentPath, fileName);
        try {
            file.createNewFile();
            System.out.println("创建成功~");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

常用方法:

  • file.getName()
  • file.getAbsolutePath()
  • file.getParent()
  • file.exists()
  • file.isFile()
  • file.isFile()
  • isDirectory()
@Test
    public void info() {
        //先创建文件对象
        File file = new File("e:\\news1.txt");
        //调用相应的方法,得到对应信息
        System.out.println("文件名字=" + file.getName());
        //getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
        System.out.println("文件绝对路径=" + file.getAbsolutePath());
        System.out.println("文件父级目录=" + file.getParent());
        System.out.println("文件大小(字节)=" + file.length());
        System.out.println("文件是否存在=" + file.exists());//T
        System.out.println("是不是一个文件=" + file.isFile());//T
        System.out.println("是不是一个目录=" + file.isDirectory());//F
    }


16.1  IO流原理及分类



基本介绍:

  • I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输
  • Java程序中,对于数据的输入/输出操作以"流(stream)"的方式进行
  • java.io包下提供了各种"流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
  • 输入input:读取外部数据(磁盛、光盘等存储设备的数据)到程序(内存)中
  • 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中

流的分类:

  • 按操作数据单位不同分为:字节流(8 bit),字符流(按字符)
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为:节点流,处理流/包装流
    网络异常,图片无法展示
    |

    网络异常,图片无法展示
    |


16.2 节点流和处理流



基本介绍:

  • 节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter
  • 处理流(也叫包装流)是 “连接〞在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,如BufferedReader、BufferedWriter
    网络异常,图片无法展示
    |

区别和联系:

  1. 节点流是底层流/低级流,直接跟数据源相接
  2. 处理流包装节点流,既可以消除不同节点流的实现差异,也可以提供重方便的方法来完成输入输出
  3. 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连

优点:

  • 性能的提高:主要以增加缓冲的方式来提高输入输出的效率
  • 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用重加灵活方便

模拟:

public class Test_ {
    public static void main(String[] args) {
        BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_());
        bufferedReader_.readFiles(10);
        BufferedReader_ bufferedReader_2 = new BufferedReader_(new StringReader_());
        bufferedReader_2.readString(5);
    }
}



16.3 输入流⭐️



基本介绍:
网络异常,图片无法展示
|


16.3.1  InputStream


16.3.1.1  FileInputStream



  • 网络异常,图片无法展示
    |

常用方法:


使用方法:

public class FileInputStream_ {
    public static void main(String[] args) {
    }
    /**
     * 演示读取文件...
     * 单个字节的读取,效率比较低
     * -> 使用 read(byte[] b)
     */
    @Test
    public void readFile01() {
        String filePath = "e:\\hello.txt";
        int readData = 0;
        FileInputStream fileInputStream = null;
        try {
            //创建 FileInputStream 对象,用于读取 文件
            fileInputStream = new FileInputStream(filePath);
            //从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
            //如果返回-1 , 表示读取完毕
            while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char)readData);//转成char显示
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭文件流,释放资源.
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 使用 read(byte[] b) 读取文件,提高效率
     */
    @Test
    public void readFile02() {
        String filePath = "e:\\hello.txt";
        //字节数组
        byte[] buf = new byte[8]; //一次读取8个字节.
        int readLen = 0;
        FileInputStream fileInputStream = null;
        try {
            //创建 FileInputStream 对象,用于读取 文件
            fileInputStream = new FileInputStream(filePath);
            //从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
            //如果返回-1 , 表示读取完毕
            //如果读取正常, 返回实际读取的字节数
            while ((readLen = fileInputStream.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen));//显示
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭文件流,释放资源.
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


16.3.1.2  BufferedInputStream



  • Bufferedlnputstream是字节流在创建 Bufferedlnputstream时,会创建一个内部缓沖区数组

基本介绍:


  • 网络异常,图片无法展示
    |

常用方法:


例:

public class BufferedCopy02 {
    public static void main(String[] args) {
//        String srcFilePath = "e:\\Koala.jpg";
//        String destFilePath = "e:\\hsp.jpg";
//        String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";
//        String destFilePath = "e:\\hsp.avi";
        String srcFilePath = "e:\\a.java";
        String destFilePath = "e:\\a3.java";
        //创建BufferedOutputStream对象BufferedInputStream对象
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            //因为 FileInputStream  是 InputStream 子类
            bis = new BufferedInputStream(new FileInputStream(srcFilePath));
            bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
            //循环的读取文件,并写入到 destFilePath
            byte[] buff = new byte[1024];
            int readLen = 0;
            //当返回 -1 时,就表示文件读取完毕
            while ((readLen = bis.read(buff)) != -1) {
                bos.write(buff, 0, readLen);
            }
            System.out.println("文件拷贝完毕~~~");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
            try {
                if(bis != null) {
                    bis.close();
                }
                if(bos != null) {
                    bos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

16.3.1.3  ObjectInputStream



基本介绍:

  • Objectlnputstream 提供反序列化功能
  • 反序列化就是在恢复数据时,恢复数据的值和数据类型
  • 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:Serializable和Externalizable

例:

public class ObjectInputStream_ {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //指定反序列化的文件
        String filePath = "e:\\data.dat";
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
        //解读
        //1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
        //2. 否则会出现异常
        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readChar());
        System.out.println(ois.readDouble());
        System.out.println(ois.readUTF());
        //dog 的编译类型是 Object , dog 的运行类型是 Dog
        Object dog = ois.readObject();
        System.out.println("运行类型=" + dog.getClass());
        System.out.println("dog信息=" + dog);//底层 Object -> Dog
        //这里是特别重要的细节:
        //1. 如果我们希望调用Dog的方法, 需要向下转型
        //2. 需要我们将Dog类的定义,放在到可以引用的位置
        Dog dog2 = (Dog)dog;
        System.out.println(dog2.getName()); //旺财..
        //关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
        ois.close();
    }
}



16.3.2  Reader


16.3.2.1  FileReader



常用方法:

  • read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
  • read(char):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1

方法使用:

public class FileReader_ {
    public static void main(String[] args) {
    }
    /**
     * 单个字符读取文件
     */
    @Test
    public void readFile01() {
        String filePath = "e:\\story.txt";
        FileReader fileReader = null;
        int data = 0;
        //1. 创建FileReader对象
        try {
            fileReader = new FileReader(filePath);
            //循环读取 使用read, 单个字符读取
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 字符数组读取文件
     */
    @Test
    public void readFile02() {
        System.out.println("~~~readFile02 ~~~");
        String filePath = "e:\\story.txt";
        FileReader fileReader = null;
        int readLen = 0;
        char[] buf = new char[8];
        //1. 创建FileReader对象
        try {
            fileReader = new FileReader(filePath);
            //循环读取 使用read(buf), 返回的是实际读取到的字符数
            //如果返回-1, 说明到文件结束
            while ((readLen = fileReader.read(buf)) != -1) {
                System.out.print(new String(buf, 0, readLen));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
目录
相关文章
|
存储 Java
【JavaSE】基础笔记 - 图书管理系统(保姆教程,含源码)
【JavaSE】基础笔记 - 图书管理系统(保姆教程,含源码)
311 1
|
6月前
|
IDE Java 测试技术
黑马全套Java教程(十)(一)
黑马全套Java教程(十)
30 0
|
8月前
|
Java 编译器
JavaSE基础精选-1基础语法
JavaSE基础精选-1基础语法
46 0
|
存储 设计模式 算法
JavaSE笔记--韩顺平(入门详细版)(一)
JavaSE笔记--韩顺平(入门详细版)(一)
535 3
|
安全 Java 编译器
JavaSE笔记--韩顺平(入门详细版)(五)
JavaSE笔记--韩顺平(入门详细版)(五)
151 4
|
存储 网络协议 安全
JavaSE笔记--韩顺平(入门详细版)(四)
JavaSE笔记--韩顺平(入门详细版)(四)
177 2
|
存储 XML 安全
JavaSE笔记--韩顺平(入门详细版)(二)
JavaSE笔记--韩顺平(入门详细版)(二)
280 0
|
存储 Oracle IDE
javase基础教程(1)| 青训营笔记
javase基础教程(1)| 青训营笔记
82 0
javase基础教程(1)| 青训营笔记
|
存储 安全 Java
黑马全套Java教程(一)(上)
黑马全套Java教程(一)
260 0
黑马全套Java教程(一)(上)
|
Java
黑马全套Java教程(六)(上)
黑马全套Java教程(六)
109 0
黑马全套Java教程(六)(上)