本帖最后由 小石姐姐 于 2018-8-3 09:05 编辑
集合、异常、线程
一. 集合 (一)数据存储的常用结构:栈, 队列, 数组, 链表, 红黑树 1.栈:先进后出(FILO , First In Last Out), 入口和出口在同一侧 适用场景: 栈内存:main方法先进栈调用,main方法中的其他方法都调用完毕后,main才能出栈 反转内容:车尾变车头,进去再出来就反转了 2.队列:先进先出(FIFO, First In First Out), 入口和出口在集合的两侧 适用场景:秒杀,抢购,在线售票, 高并发场景 3.数组: 查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过数组的索引可以快速查找某一个元素 增删慢:数组的长度是固定的,我们想要增加/删除一个元素,必须创建一个新数组,把原数组的数据复制过来 4.链表: 查询慢:链表中的地址不是连续的,每次查询元素,都必须从头开始查询 增删快:链表结构,增加/删除一个元素,对链表的整体结构没有影响,所以增删快 单向链表:链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序有可能不一致) 双向链表:链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合 适用场景:查询少,增删多的场景 5.红黑树:是一种 平衡 二叉 查找 树 特点:趋近于平衡树,查询的速度非常的快,查询叶子节点最大次数和最小次数不能超过2倍 添加元素到红黑树:元素自动排序 左小,右大. 查找树的遍历: 从根节点开始,每个节点做3个操作: ①先往左找有没有子节点,直到没有左子节点 ②返回当前节点的数据 ③往右找右子节点,右边没有则返回父节点 (二)List接口 1.接口的特点: ①有序的集合,存储元素和取出元素的顺序是一致的(存储132 取出123) ②有索引,包含了一些带索引的方法 ③允许存储重复的元素 2.接口特有的方法: [AppleScript] 纯文本查看 复制代码 ①void add(int index , E element): 将指定的元素,添加到指定索引的位置上[/align] ②E remove (int index): 移除指定位置的元素,返回的是被移除的元素
③E set ( int index, E element): 用指定的元素替换集合中指定位置的元素,返回的是原来的元素
④E get( int index): 返回集合中指定位置的元素. 3.List子集合 (1).ArrayList集合 底层为数组结构,查询快,增删慢,线程不安全,效率高 (2).LinkedList集合 特点:①底层是一个链表结构:查询慢,增删快 ②里边包含了大量操作首尾元素的方法 注意:使用LinkedList集合特有的方法,不能使用多态 LinkedList方法: [AppleScript] 纯文本查看 复制代码 ①void addFirst(E e):将指定元素插入此列表的开头[/align] ②void addLast(E e):将指定元素插入此列表的结尾
③void push(E e):将元素推入此列表所表示的堆栈,等效于addFirst(E e)
④E getFirst( ): 获取此列表中的第一个元素
⑤E getLast( ):获取此列表中的最后一个元素
⑥E removeFirst( ):移除并返回列表的第一个元素
⑦E removeLast( ):移除并返回此列表的最后一个元素
⑧E pop( ):从列表所表示的堆栈处弹出一个元素,等效于 removeFirst( ) (三). Set接口:元素不可重复,无索引 1.HashSet类:底层是哈希表,元素无序,元素不可重复(用hashCode()和equals()方法来判断) 获取元素时,先获取hashCode值进行比较,如果相同,则是哈希冲突,进行equals判断如果不重复,则添加 如果重复,则哈希冲突,再比较相同哈希值元素的equals() 如果equals()方法比较有相同的,则元素重复,不添加 如果equals()方法比较没有相同的,则没有重复的,添加 2.哈希值:是一个十进制的整数,由系统随机给出,一般是通过将对象的内部地址转换成一个整数来实现的 3.哈希表: JDK 8以前 : 哈希表 = 数组 + 链表 JDK 8以后 : 哈希表 = 数组 + 链表/红黑树(提高查询的速度); 特点 : 速度快 4.LinkedHashSet类:哈希表+链表 特点:①元素存取有序(存入和取出顺序一致) ②元素不可重复 ③没有索引 总结:要存储的元素可以重复的,用List集合: 增删少,用ArrayList 增删多,用LinkedList 要存储的数据要求不重复,或者相对一个集合去重,用Set集合: 无序存取,用HashSet 有序存取,用LinkedHashSet (四), 可变参数 1.使用前提:当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数. 2.使用格式:定义方法时使用 修饰符 返回值类型 方法名( 数据类型...变量名){} 3.可变参数的原理: 可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数传递的参数个 数,可以是0个,1,2...多个 4.注意事项: ①一个方法的参数列表,只能有一个可变参数 ②如果方法的参数有多个,那么可变参数必须写在参数列表的末尾 (五), Collections集合工具类 [AppleScript] 纯文本查看 复制代码 1.static?<T>?boolean?addAll(Collection<T>?c,?T...?elements) :往集合中添加一些元素[/align] 2.static?void?shuffle(List<?>?list) :打乱集合顺序
3.static?<T>?void?sort(List<T>?list) :将集合中元素按照默认规则排序 需要实现Comparable<E>接口,重写 int compareTo(E e)方法 大小的比较通过compareTo( )方法的返回值确定: 如果是负数: 当前元素比被比较元素小 如果是0: 当前元素与被比较元素相等 如果是整数: 当前元素比被比较元素大 规则: this-参数: 升序 参数-this:降序 4.[AppleScript] 纯文本查看 复制代码 static?<T>?void?sort(List<T>?list,Comparator<??super?T>?) :将集合中元素按照指定规则排序 (六),Map集合: java.util.Map<k,v>集合 (1)Map集合的特点: ①.Map集合是一个双列集合,一个元素包含两个值(一个key,一个value) ②.Map集合中的元素,key和value的数据类型可以相同,也可以不同 ③.key不允许重复,value可以重复 ④.key和value是一一对应的,一个键只能对应一个值 Map集合适合存储一对一关系的数据 (2)Map中的子类: 1.HashMap集合的特点: ①.Hashmap集合底层是哈希表,是一个线程不安全的集合,是多线程的集合,查询的速度特别快 ②.无序,存储和取出的顺序有可能不一致 2.LinkedHashMap的特点: ①.LinkedHashMao集合底层是哈希表+链表(保证迭代的顺序) ②.LinedHashMap集合是一个有序的集合,存储和取出的顺序是一致的 补充: HashSet底层使用的就是HashMap LinkedHashSet底层使用的就是LinkedHashMap 3.Hashtable集合底层也是哈希表,是一个线程安全的集合,是单线程的集合,查询速度慢 对比: ①Hashtable不允许存储null值和null键,而HashMap可以 ②Hashtable线程安全但效率低,而HashMap线程不安全但效率高 (3)Map中的常用方法
[AppleScript] 纯文本查看 复制代码 [/align][align=left] 1. V?put(K?key,?V?value): 添加/修改键值对. 如果键存在, 则用新值替换旧的值[/align][align=left] 2. V?remove(Object?key): 根据键删除整个键值对, 返回被删除元素的值 [/align][align=left] 3. V?get(Object?key): 根据键获取值. 如果键不存在, 则返回null [/align][align=left] 4. boolean containsKey(Object key): 判断是否包含指定的键[/align][align=left] 5. Set<K>?keySet(): 获取Map集合中所有的键, 存储到Set集合中 6. Set<Map.Entry<K,V>>?entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合) (七), JDK9对集合添加的优化 List接口,Set接口,Map接口:里边增加了yige静态的方法of,可以给集合一次性添加多个元素 使用前提:当集合中存储的元素的个数已经确定了,不再改变时使用. 注意事项:①of方法只适用于List接口,Set接口,Map接口,不适用于接口的实现类 ②.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常 ③.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常 (八), Debug 1. 断点: breakpoint, 在debug模式下, 程序运行到断点会暂停住, 便于我们在程序运行过程中查看 2. Debug调试程序: 可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug 使用方式: 在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里) 右键,选择Debug执行程序, 程序就会停留在添加的第一个断点处 3. 执行程序: [AppleScript] 纯文本查看 复制代码 f8:逐行执行程序 f7:进入到方法中 shift+f8:跳出方法 f9:跳到下一个断点,如果没有下一个断点,那么就结束程序 ctrl+f2:退出debug模式,停止程序 Console:切换到控制台
二, 异常 [AppleScript] 纯文本查看 复制代码 1.异常:指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止 [/align][align=left] 2.分类: ①Exception:编译期异常,写代码时出现的问题 ②RuntimeException:运行期异常,程序运行过程中出现的问题 try{ //可能会出现异常的代码 }catch(Excaption e){ //异常的处理逻辑 } Error:错误,绝症,必须将程序停下来,修改代码才能解决[/align][align=left] 3.异常关键字throw ①.throw关键字必须写在方法的内部 ②.throw关键字后边new的对象必须是Exception或者Exception的子类对象 ③.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象 throw后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JVM去处理(打印异常对象,中断程序) throw后边创建的是编译异常(写代码的时候报错),我们就必须处理[/align][align=left] 4.throws关键字:异常处理的第一种方式,交给别人处理 ①使用格式:在方法声明时使用 修饰符 返回值类型 方法名(参数裂变) throws AAAException, BBBException...{ throw new AAAException("产生原因"); throw new BBBException("产生原因"); ..... } ②注意事项:a.throws关键字必须写在方法声明处
b.throws关键字后边声明的异常必须是Exception或者是其子类 c.方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常 如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可 d.调用了一个声明抛出异常的方法,我们就必须得处理声明的异常 要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM 要么try...catch自己处理异常[/align][align=left] 5.try...catch (捕获异常) :异常处理的第二种方式,自己处理异常 ①格式: try{ //可能产生异常的代码 }catch(定义一个异常的变量,用来接收try中抛出的异常对象){ //产生异常对象之后,怎么处理异常对象 //一般在工作中,会把异常的信息记录到一个日志中 } . . . catch(异常类名 变量名){ } ②注意: try中可能会抛出多个异常对象,可以用多个catch来处理 如果try中出现了异常,那就会执行catch中的异常处理逻辑,然后继续执行之后的代码 如果try中没有异常,那就不会执行catch中的异常处理逻辑,而是直接继续执行后面的代码 6.throwable中的3个异常处理方法 ①String getMessage( ): 异常的信息,没有原因返回null ②String toString( ): 异常的类型和原因信息 ③void printStackTrace( ): 使用标准错误输出流打印异常信息 [/align][align=left] 7.finally代码块 格式: try{ 可能产生异常的代码 }catch(定义一个异常的变量,用来接收try中抛出的异常对象){ //产生异常对象之后,怎么处理异常对象 //一般在工作中,会把异常的信息记录到一个日志中 } . . . catch(异常类名 变量名){ }finally{ 无论是否出现异常都会执行 } 注意:①:finally不能单独使用,必须和try一起使用 ②:finally一般用于资源释放(资源回收),无论此程序是否出现异常,最后都要资源释放 8.异常注意事项: ( 1 )捕获多个异常的3个方式 ①多个异常分别处理(一个try捕获一个异常,并对应一个catch) ②多个异常一次捕获,多次处理(一个try捕获多个异常,并对应多个catch) 如果catch里面定义的异常变量有子父类的关系,那么子类的异常变量必须写在上边,否则会报错 ③多个异常一次捕获一次处理(一个try捕获多个异常,并对应一个catch) ( 2 )finally中有return语句 则永远返回该return语句的值,应该避免在finally中写return语句 ( 3 )子父类继承重写方法时的异常要求(简记:父类异常什么样,子类异常也就什么样) ①.如果父类抛出多个异常,子类重写父类方法时也抛出相同的异常,或者是父类异常的子类,或者不抛出异常 ②.如果父类没有抛出异常,子类重写该方法时也不可抛出异常.此时子类产生该异常,只能捕获处理,不能抛出 9.自定义异常: 格式: public class XXXException extends Exception / RuntimeException{ 添加一个空参数的构造方法 添加一个带异常信息的构造方法 } 注意: ①.自定义异常类一般都是以Exception结尾,说明该类是一个异常类 ②.自定义异常类,必须要继承Exception或者RuntimeException 如果继承的是编译时期的异常,则必须抛出或捕获处理 如果继承的是运行时期的异常,则不用管,让JVM去处理 三, 多线程 (一)计算机基本概念 1.并发和并行 并发:指两个或多个事件在同一时间段内同时发生 并行:指两个或多个事件在同一时刻发生(同时发生) 2.进程与线程 进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程; 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程 多线程的好处: 效率高,多个线程之间互不影响 3.线程的调度 分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间. 抢占式调度:优先级高的优先使用cpu,若优先级相同,则随机选择一个线程执行. (Java使用) 4.主线程 主要执行的(main)线程 (二)创建多线程 1.第一种方式: 继承Thread类 ①定义类,继承Thread类 ②重写run( )方法,run方法内部是线程要执行的任务 ③创建Thread子类的对象,调用start( )方法启动线程 a.注意: 必须调用start( )方法来开启线程,不能直接调用run( )方法,否则就会变成单线程. 同一个线程对象,不能多次调用start( )方法 b.Thread中的常用方法: (1)获取线程的名称: ①使用Thread类中的方法getname( ) String getName( ) 返回该线程的名称. ②可以先获取到当前正在执行的线程,使用线程的方法getName( )获取线程的名称 static Thread currentThread 返回对当前正在执行的线程对象的引用 2.第二种方式:实现Runnable接口 ①定义类,实现Runnable接口 ②重写run( )方法 ③创建Runnable实现类对象 ④创建Thread类对象,在构造方法中传入Runnable实现类对象 ⑤通过Thread对象调用statrt( )方法启动线程 3.Thread和Runnable两种方式的区别 实现Runnable接口创建多线程程序的好处: ①避免了单继承的局限性 : 一个类只能继承一个类,如果继承了Thread类就不能继承其他类了,实现Runnable接口的方式还可以继承其他的类,实现其他的接口 ②增强了程序的扩展性,降低了程序的耦合性 : 线程是Thread,任务是Runnable接口,相当于将线程和任务分开了 4第三种方式: 匿名内部类创建线程 匿名内部类的三种方式:代码如下 public class Test {
[AppleScript] 纯文本查看 复制代码 public static void main(String[] args) {
// 继承Thread类, 使用匿名内部类简化
new Thread("继承Thread类") {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}.start();
// 实现Runnable接口, 使用匿名内部类简化
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
};
new Thread(r, "可以设置名字").start();
// 更简单的方式
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}, "实现Runnable接口").start();
}
}
5.线程安全问题 : 多线程访问共享数据,CPU高速切换,在操作共享数据时还未完成,其他线程又进行操作 6.解决线程安全问题方式: ①同步代码块:使用synchronized关键字修饰的代码块,并传入一个当作锁的对象 格式: synchronized(锁对象){ 可能会出现线程安全问题的代码(访问了共享数据的代码) } ②同步方法: 使用synchronized关键字修饰的方法,具有默认的锁对象 非静态同步方法的锁对象:this 静态同步方法的锁对象:本利的class属性-->class本间对象反射() 格式:修饰符 synchronized 返回时类型 方法名(参数列表){ 可能会出现问题的代码; } 拓展: 获取一个类的字节码对象的3种方式: 1.类名.class属性 2.对象.getClass( ) 3.Class.forName(字符串的类的全名) ③锁机制: [AppleScript] 纯文本查看 复制代码 java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口 // 成员方法 void lock(): 获取锁 void unlock(): 释放锁 java.util.concurrent.locks.ReentrantLock类: Lock的实现类[/align] // 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个 Lock lock = new ReentrantLock();
@Override
public void run() { // 加锁 lock.lock(); try { // 操作共享变量的代码... } finally { // 在finally中保证释放锁 lock.unlock(); } }
7.线程的状态: 1.1线程的6种状态与生命周期 线程的生命周期中, 可以出现有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()方法结束(执行结束, 或内部出现异常), 则进入此状态 1.2 Object类中关于线程的方法: // 成员方法 (只能通过"锁对象"调用) [AppleScript] 纯文本查看 复制代码 void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程 void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程 void wait(): 让当前线程处于无限等待状态 void wait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态 void wait(longtimeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态[/align]
|
|