黑马程序员技术交流社区

标题: [石家庄校区]第二阶段day03-day06 [打印本页]

作者: GIAO    时间: 2018-11-20 15:35
标题: [石家庄校区]第二阶段day03-day06
                                                                                                            第二阶段day03-day06
day03
1.常见的数据结构
数据存储的常用结构有:栈、队列、数组、链表和红黑树。
:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
先进后出,入口、出口的都是栈的顶端位置。

队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
先进先出,队列的入口、出口各占一侧(类似火车过隧道,头先从入口进,头也先从出口出)

数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。查询快,增删慢.
查找元素快:通过索引,可以快速访问指定位置的元素,
指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。

链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表

[attach]259543[/attach][attach]259542[/attach]


查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
增删元素快:增加元素:只需要修改连接下个元素的地址即可。

红黑树
二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍[attach]259545[/attach]

2.List接口
java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
List接口特点:
1. 元素存取有序 (存入和取出元素的顺序一致) 321->321  排序: 从小到大
        2. 元素可以重复  1 1 1 1
        3. 有索引,与数组的索引是一个道理.
List子体系中的实现类都具有上述特点

java.util.List接口:
        // 常用特有成员方法 (都是按照索引来操作的)   
        void add(int index, E element): 将指定的元素, 添加到该集合中的指定位置上
        E get(int index): 返回集合中指定位置的元素
        E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素
        E set(int index, E element): 用指定元素替换集合中指定位置的元素, 返回值的更新前的元素
  list.add("嗯嗯"): 往 尾部添加 指定元素
list.add(1,"没头脑")往指定位置添加
list.remove(2)删除索引位置为2的元素
list.set(0, "三毛")在指定位置 进行 元素替代(改)
跟size() 方法一起用  来 遍历的
            for(int i = 0;i<list.size();i++){
                    System.out.println(list.get(i));
2.1List的子类
ArrayList集合
ArrayList集合:ArrayList底层的数据结构: 数组
ArrayList的特点:查询快,增删慢,线程不安全, 效率高
ArrayList适用场景:存储的数据"查询多, 增删少"的场景. 如用一个ArrayList存储中国城市名称

LinkedList集合
LinkedList底层的数据结构: 链表
LinkedList的特点:查询慢,增删快,线程不安全, 效率高
LinkedList适用场景:存储的数据"查询少, 增删多"的场景. 如用LinkedList实现栈或队列
java.util.LinkedList<E>类: 链表结构, 查询慢, 增删快
        // 特有成员方法(主要操作开头和末尾元素)
        void addFirst(E e): 将指定元素插入此列表的开头
        void addLast(E e): 将指定元素添加到此列表的结尾
        E getFirst(): 返回此列表的第一个元素
        E getLast(): 返回此列表的最后一个元素
        E removeFirst(): 移除并返回此列表的第一个元素
        E removeLast(): 移除并返回此列表的最后一个元素
        E pop(): (其实就是removeFirst())从此列表所表示的栈中弹出一个元素
        void push(E e): (其实就是addFirst())将元素添加到此列表所表示的栈中
[attach]259546[/attach]
2.2Set接口:Set集合体系特点:1. 元素不可重复2. 没有索引

HashSet特点:
1. 元素不可重复
2. 没有索引
3. 元素存取无序 (存入和取出顺序有可能不一致)
4. 底层采用 哈希表 结构. (查询快)
哈希表 = 数组 + 链表或红黑树
java.util.HashSet类:
        // 常用方法
        boolean add(E e): 添加元素, 根据元素的 hashCode() 和 equals() 方法判断是否重复. 重复则不添加并返回false, 不重复则添加并返回true

LinkedHashSet集合
LinkedHashSet特点:
        1. 元素存取有序 (存入和取出顺序一致)
        2. 元素不可重复
        3. 没有索引
       
LinkedHashSet底层数据结构:
        哈希表 + 链表   (也就是: 数组 + 链表或红黑树 + 链表)
        其中, 哈希表用于存储数据, 额外的链表用于记录元素添加时的先后顺序, 以便在获取元素时保持顺序一致


总结: 什么时候用List, 什么时候用Set?
        要存储的元素可以重复的, 用List集合:
                增删少, 用ArrayList
                增删多, 用LinkedList
        要存储的数据要求不重复, 或者相对一个集合去重, 用Set集合:
                不要求存取顺序一致, 用HashSet
                要求存取顺序一致, 用LinkedHashSet

Collection接口: 单列集合的根接口, 规定了公共的功能
        |
        |_ List接口: 元素存取有序, 可重复, 有索引
        |        |_ Vector类: 底层数组, 有索引, 内存空间连续分配, 查询快, 增删慢, 线程安全, 效率低
        |        |_ ArrayList类: 底层数组, 有索引, 内存空间连续分配, 查询快, 增删慢, 线程不安全, 效率高
        |        |_ LinkedList类: 底层链表, 查询慢, 增删快
        |        |_ 遍历
        |                - toArray(): 可以
        |                - 普通for: 可以
        |                - 增强for: 可以
        |                - 迭代器: 可以
        |
        |_ Set接口: 元素不可重复, 无索引
                |_ HashSet类: 底层哈希表, 元素无序, 元素不可重复(用hashCode()和equals()方法来判断)
                |        |_ LinkedHashSet类: 哈希表+链表, 同时具有HashSet的元素不重复, 链表存取有序的特点
                |
                |_ TreeSet类: 底层红黑树结构(存入元素时就会按照元素自然顺序排序).
                |       
                |_ 遍历
                        - toArray(): 可以
                        - 普通for: 不可以, 没有索引, 不能用!
                        - 增强for: 可以
                        - 迭代器: 可以
day04
java.util.Map接口: 双列集合的顶层
        // 成员方法
        V put(K key, V value): 添加/修改 键值对.
            如果键存在, 则用新值替换已有值, 返回被替换的值; 如果键不存在, 添加键值对, 返回null
        V remove(Object key): 根据键删除键值对, 返回被删除元素的值
                如果键不存在, 返回null
        V get(Object key): 根据键获取值.
            如果键不存在, 则返回null
        boolean containsKey(Object key): 判断是否包含指定的键

        Set<K> keySet(): 获取Map集合中所有的键, 存储到Set集合中
        Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的Entry对象的集合(Set集合)
Map遍历方式一: keySet()方法实现通过键找值
Hashmap<String,Integer>map=new HashMap<>();
map.put( , );
map.put(  ,);
map.put( , );
Set<String>keyset=map.keyset();// 通过keySet()先获取包含所有key的set集合
遍历set集合, 获取每个key
for (String key : keySet) {
            // 通过key获取value
            Integer value = map.get(key);
            // 打印键值对
            System.out.println(key + "=" + value);
        }
简化的增强for循环
        for (String key : map.keySet()) {
            // 通过key获取value
            Integer value = map.get(key);
            // 打印键值对
            System.out.println(key + "=" + value);
        }
迭代器
        Iterator<String> iterator = keySet.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            Integer value = map.get(key);
            System.out.println(key + "=" + value);
        }
Entry
java.util.Map.Entry接口:
        // 常用成员方法
        K getKey(): 获取Entry对象中的键
        V getValue(): 获取Entry对象中的值
       
Entry对象就是一个节点, 节点中存储了key和value
拿到一个Entry对象就可以从中获取key和value
Map遍历方式2: 通过entrySet()获取Entry对象形式遍历
Map<String, String> map = new HashMap<>();
        // keySet()遍历
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            // 通过Entry对象获取每个键值对
            String key = entry.getKey();
            String value = entry.getValue();
            // 打印当前键值对
            System.out.println(key + "=" + value);
        }

HashMap存储自定义数据类型作为键
HashMap存储自定义JavaBean对象作为key保证key唯一不重复, 需要让JavaBean重写 hashCode() 和 equals() 方法

LinkedHashMap类
LinkedHashMap底层: 哈希表 + 链表
        key不允许重复, 但key存取有序
Hashtable类
Hashtable和HashMap:
        相同点:
                1. 底层都是哈希表
        不同点:
                1. Hashtable不允许存储 null 值和 null 键; HashMap允许存储 null 值和 null 键
                2. Hashtable线程安全效率低; HashMap线程不安全效率高
               
Hashtable目前很少使用.
但其子类 Properties 集合, 可以与IO流结合使用, 应用较多

JDK9对集合添加的优化(of)
1. of() 方法只适用于List接口, Set接口, Map接口, 不适用于接口的实现类
2. of() 方法的返回值是一个不可变的集合, 集合不能再使用 add(), put() 方法添加元素, 会抛出异常
3. Set接口和Map接口在调用 of() 方法的时候, 不能有重复的元素, 否则会抛出异常
将 不可变集合 的元素转移到常见集合实现类中:
// List
ArrayList<Integer> list = new ArrayList<>(List.of(1,2,3));
        // Set
    HashSet<Integer> set = new HashSet<>(Set.of(1,2,3));
        // Map
    HashMap<Integer, String> map = new HashMap<>(Map.of(1,"a",2,"b"))

断点:
        breakpoint, 在debug模式下, 程序运行到断点会暂停住, 便于我们在程序运行过程中查看
Debug调试程序:
    可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug
使用方式:
    在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里)
    右键,选择Debug执行程序
    程序就会停留在添加的第一个断点处
执行程序:
    f8:逐行执行程序
    f7:进入到方法中
    shift+f8:跳出方法
    f9:跳到下一个断点,如果没有下一个断点,那么就结束程序
    ctrl+f2:退出debug模式,停止程序
    Console:切换到控制台

day05
异常的体系结构
java.lang.Throwable  // 体系最顶层
        |_ Error                  // 错误
        |_ Exception              // 编译时异常
                |_ RuntimeException   // 运行时异常
异常产生的过程:
        1. 当执行的代码发生异常操作时, JVM会创建一个对应的异常类对象, 包含异常的内容, 原因, 位置
                如 new ArrayIndexOutOfBoundsException(), new NullPointerException()
        2. 如果执行代码的方法没有对异常进行 try...catch 处理, 则会向该方法调用处的方法抛(向上层抛). 如果所有方法(包括main()方法)都没有 try...catch 处理异常, 则该异常会被JVM按照默认的处理方式处理
        3. JVM对于异常的默认处理方式是:
                1. 将异常信息(内容, 原因, 位置)打印到控制台
                2. 终止当前的程序
throw制造异常
格式:throw new 异常类名("异常原因字符串");
1. throw 必须写在方法的内部
        2. throw 后面new的异常对象, 必须是 "Exception" 或 "Excetion的子类" 的对象
        3. 一个方法内部 throw 了一个异常对象, 则该方法可以分为2种情况来处理该异常:
                如果 throw 的是"运行时异常"(RuntimeException及其子类)对象, 那么可以不处理
                        该异常最终会交给JVM处理, 结果就是: 打印异常信息到控制台, 并立刻结束程序
                如果 throw 的是"编译时异常"(Exception及其子类), 则必须处理:
                        处理方式1: throws 抛出
                        处理方式2: try...catch 捕获
异常的处理方式1: throws声明抛出异常
throws, 声明抛出 (方法自己不处理, 交给方法调用者处理, "甩锅给别人")
        作用: 告诉方法的调用者, 调用此方法有可能发生异常, 需要在编写代码时处理

格式:
        修饰符 返回值类型 方法名() throws 异常类名1, 异常类名2, ... {
内部抛出异常
           
        }
1. throws 必须写在方法声明上
        2. throws 后面的异常类名, 一般是 Exception 或 Exception的子类
                (RuntimeException及其子类也行, 但是没有什么意义)
        3. 方法内部如果抛出了多个异常对象, throws 后也必须声明多个异常
                如果抛出的异常对象有子父类关系, 那么直接声明父类异常即可
        4. 调用了一个带有 throws 声明抛出异常的方法, 就必须处理该异常:
                要么继续声明 throws 抛出
                要么 try...catch 捕获处理异常
异常处理方式2: 捕获异常
  try {
        // 可能产生异常的代码
    } catch (异常类名 变量名) {
        // 处理异常的代码
        // 一般会将异常信息存储到日志中
    }
        ...
        } catch (异常类名 变量名) {
        // 处理异常的代码
        // 一般会将异常信息存储到日志中
    }

注意:
        1. try 中可能会抛出多种异常, 就可以写多个 catch 分别处理每种异常
        2. 如果 try 中产生了异常, 就会从产生异常的那一行代码直接跳转到对应的 catch 中执行处理代码, 然后继续执行 try...catch 之后的其他代码; 如果 try 中没有产生异常, 那就不会执行 catch , 执行完 try 中的代码后, 继续执行 try...catch 之后的其他代码


finally代码块
try {
        // 可能发生异常的代码
    } catch(异常类型 异常变量名) {
        // 处理异常
    }
        ...
        catch(异常类型 异常变量名) {
        // 处理异常
    } finally {
        // 无论是否发生异常, 是否捕获, 最后都会执行的代码.
        // 通常在这里执行释放资源的操作
    }
1. finally 必须和 try...catch 一起使用
        2. finally 一般用于释放资源 (IO流时用到)
finally中有return语句,:果 finally 代码块中有 return 语句, 则永远返回 finally 中的 return 语句的值
应该避免在 finally 中写 return 语句
常注意事项3: 子父类继承重写方法时的异常要求
子父类继承关系下, 子类重写父类带有 throws 的方法:
        1. 如果父类抛出多个异常, 子类重写父类方法时可以有3种方式:
                a: 抛出和父类相同的异常
                b: 抛出父类异常的子类
                c: 不抛出异常
        2. 父类方法没有抛出异常, 子类重写父类该方法时也不可抛出异常
                此时子类产生该异常, 只能捕获处理, 不能声明抛出
   
   
一般情况下:
    父类方法声明的异常是什么样的, 子类重写的方法声明异常就什么样, 保持一致即可
// 父类方法声明抛出异常, 子类重写方法也抛出
public class Fu {
    public void method() throws IOException {
        // ...
    }
}

public class Zi extends Fu {
    @Override
    public void method() throws IOException {            // 正确, 和父类一样
    public void method() throws FileNotFoundException {  // 正确, 是子类异常
    public void method() throws {                        // 正确, 不抛出异常
        // ...
    }
}

// 父类没有异常, 子类重写方法也不能抛出
public class Fu {
    public void method() {
        // ...
    }
}

public class Zi extends Fu {
    @Override
    public void method() {
        // ...
    }
}
并发与并行
并发: (交替执行) 指两个或多个事件在"同一时间段内"发生,
依靠电脑CPU快速地在多个任务之间切换执行实现的
并行: (同时执行) 指两个或多个事件在"同一时刻"发生 (同时发生)
程序 > 进程 > 线程


day06
Thread类中的start()方法与run()方法的区别:线程对象调用run()方法不开启线程,仅是对象调用方法。线程对象调用start()方法开启线程,并让jvm调用run()方法在开启的线程中执行。


请描述创建线程的两种方式。
l 第一种方式是将类声明为 Thread 的子类。
1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
2. 创建Thread子类的实例,即创建了线程对象。
3. 调用线程对象的start()方法来启动该线程。
l 第二种方式是声明一个类实现Runnable 接口。
1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2. 创建Runnable实现类的实例,并以此实例作为Threadtarget来创建Thread对象,Thread对象才是真正的线程对象。
3. 调用线程对象的start()方法来启动线程。

请描述实现Runnable接口比继承Thread类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
4. 线程池只能放入实现Runablecallable类线程,不能直接放入继承Thread的类。

线程安全出现的原因:多个线程操作共享的数据
同步技术:
C:/Users/Administrator/AppData/Local/YNote/data/qq43AEA454F9E8FC8D3CE99A4053144AD8/a1b1e3e75f3a4aa5a23fa90c25a4544e/clipboard.png
synchronized (锁对象) {
    // 操作共享数据的代码
}

锁对象:
        类型: 任意
        必须是多个线程共享的唯一一个对象
锁对象, 也称为"同步锁", "对象锁", "对象监视器"
同步方法:public synchronized void method(){
           可能会产生线程安全问题的代码}
对于非static方法,同步锁就是this。  
​      对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
Lock锁:public void lock():加同步锁。
              public void unlock():释放同步锁。
new Reentrantlock()


同步的原理:
    线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象
    进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码
    此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
    当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
    等在同步代码块外的其他线程就可以继续争夺锁对象


线程的生命周期中, 可以出现有6种状态:
    1. "NEW 新建"
       线程被创建, 但没有调用 start() 启动
    2. "RUNNABLE 可运行"
       调用 start()方法后已启动, 但可能正在执行 run() 方法的代码, 也可能正在等待CPU的调度
    3. "BLOCKED (锁)阻塞"
       线程试图获取锁, 但此时锁被其他线程持有
    4. "WAITING 无限等待"
       通过锁对象调用无参的 wait() 进入此状态.
       等待其他线程通过锁对象执行 notify() 或 notifyAll() 才能结束这个状态
    5. "TIMED_WAITING 计时等待"
       如通过锁对象调用有参的 wait(long millis) 或 sleep(long millis), 则进入此状态.
       直到时间结束之前被其他线程通过锁对象执行 notify()或 notifyAll()唤醒, 或时间结束自动唤醒
    6. "TERMINATED 终止"
       run()方法结束(执行结束, 或内部出现异常), 则进入此状态












欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2