7. 集合(存储引用类型)
主要有三种集合:
- List: 有序集合,可以放重复的数据
- Set:无序集合,不能重复
- Map:无序集合,键值对
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CK4j5u8o-1612419431401)(C:\Users\LiTangMM\AppData\Roaming\Typora\typora-user-images\image-20191202110439361.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uaXFAq5S-1612419431402)(C:\Users\LiTangMM\AppData\Roaming\Typora\typora-user-images\image-20191202111325514.png)]
Collection接口
public class CollectionTest(){
boolean add(E element);
void clear();
boolean isEmpty();
int size();
Object[] toArray();
Iterator<E> iterator(); //返回集合类依赖的迭代器对象
boolean contains(Object o); //调用集合元素equals方法比较
boolean remove(Object o);
//建议:使用迭代器删除集合元素
// 增强for循环
}
泛型
一种未知的数据类型,当我们不知道用什么类型的时候,可以使用泛型
泛型也可以是个变量,用来接收数据类型
好处:
- 避免了类型转换的麻烦,存储的是什么类型,去除的就是出什么类型
- 把运行期异常提升到了编译期
在创建对象时,会把数据类型作为参数传递,确定数据类型
// 泛型类
pubilc class GenericClass<E> {
private E name;
public E getName(){
return name;
}
public void setName(E name){
this.name = name;
}
}
// 泛型方法
// 定义在修饰符和返回类型之间
public class GenericMethod{
public <M> void method01(M m){
sout(m);
}
public static <M> void method02(M m){
sout(m)
}
}
// 泛型接口
//...
//泛型通配符 ?
//ArrayList<Interger>:ArrayList<?>:ArrayList<String>
public static void printArray(ArrayList<?> list){
Interator<?> it = list.iterator();
while(it.hasNext()){
Object o = it.next();
sout(0);
}
}
//泛型型上下限
//泛型没有继承
<? extends E>:代表使用的泛型只能是E类型的子类或自己
<? super E>:只能是E类型的父类或自己
mainclass{
main{
GenericClass<String> gc = new GenericClass<>();
GenericClass<Integer> gc = new GenericClass<>();
GenericMethod gcc = new GenericMethod();
gcc.method("nihao");
gcc.method(123);
}
}
List集合
List 接口
java.util.List extends Coolection
特点:
- 有序的集合
- 有索引
- 允许存储重复的元素
特有方法(与索引有关)注意索引越界异常:
List Methods{
public void add(int index,E element);
public E get(int index);
public E remove(int index);
public E set(int index,E element);
}
ArrayList集合
底层是数组,线程不安全的
添加时会复制原数组再添加
LinkedList集合
底层是双向链表(有首尾指针),线程不安全的
LinkedList methods{
public E getFist();
public E getLast();
public void addFirst(E e);
public E removeFirst();
public void addLast(E e);
public E removeLast();
public push(E e);
public E pop();
}
Vector集合
可以实现可怎张的对象数组,与ArrayList类似,线程安全的
Set集合
Set接口
java.util.Set extends Collection
- 不允许存储重复的元素(元素重写了hashCode equals方法)
- 没有索引,无带索引的方法
add方法会调用对象的hashCode方法,计算哈希值,在集合中找有没有改哈希值的元素,没有则存储;有则调用equals方法,查找是否有相等的元素,没有则存储.
HashSet集合
java.util.HashSet implements Set
哈希表结构,无序
jdk1.8版本之前 哈希表=数组+链表
jdk1.8版本之后 哈希表=数组+红黑树(链表长度超过8个元素自动转换,提高查询速度)
LinkedHashSet集合
java.util.LinkedHashSet implements HashSet
底层是一个哈希表+链表. 多了一条链表,保证元素有序
可变参数(数组)
jdk1.5之后出现的新特性
使用前提:
当方法的参数列表已经确定,但是参数个数不确定,就可以使用可变参数.
注意事项:
- 一个方法的参数列表,只能有一个可变参数.
- 如果方法的参数有多个,可变参数必须写在参数列表的末尾
public class Demo01VarArgs{
public static void main(String[] args){
add(1,2,3,4,5); //将会创建一个长度为5的数组
}
public static int add(int...arr){
int res = 0;
for(int var:arr){
res += var;
}
return res
}
}
Map集合
双列集合
常用子集
HashMap: 存数据采用的哈希表结构元素的存取顺序不能保证一致. 由于要保证键的唯一 不重复,需要重写简单hashCode()方法 equals()方法
LinkedHashMap: HahsMap下的一个子类,采用哈希表接口+链表结构
常用方法
public put(K key,V value)
public remove(Object key)
public get(Object key)
public Set<K> keySet()
public Set<Map.Entry<K,V>> entrySet()
package demo.hashmap;
import java.util.HashMap;
public class learnTest {
public static void main(String[] args) {
String s = "aabbccddeeffaabbddddefda";
HashMap<Character,Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if(map.containsKey(c)){
map.put(c,map.get(c).intValue()+1);
}else{
map.put(c,1);
}
}
for (Character key:map.keySet()) {
System.out.println(key+":"+map.get(key));
}
}
}
8. 多线程
并发与并行
并发:指多个事件在同一时间发生(单核)
并行:至多个事件在同一时刻发生(多核)
线程
创建多线程程序的第一种方式:创建Thread类的子类
package demo.Thread;
/*
创建多线程程序的第一种方式:创建Threa的子类
java.lang.Thread类:是描述线程的类,要继承Thread类
实现步骤:
1. 创建一个Thred类的子类
2. 在Thread类的子类中重写Thread的run方法,设置线程任务
3. 创建Thread类的子类对象
4. 调用start方法,开启新的线程去执行run方法
结果1是两个线程并发执行
多次启动一个线程是非法的.特别是当线程已经执行结束后,不能再重新启动
java程序属于抢占式调度,哪个优先级包
*/
public class Demo01Thread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:"+i);
}
}
}
package demo.Thread;
import java.lang.Thread;
public class MyThread extends Thread {
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("thread:"+i);
}
}
}
获取线程的名称
-
使用Thread类中的getName方法
-
可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程的名称
String getName() : 返回线程的名称
static Thread currentThread() : 返回对当前正在执行的线程对象的引用
-
默认名称:
主线程: main 其他: Thread-0,Thread-1,Thread-2
设置线程名称
- 使用Thread类中的方法setName(名字)
- void setName(String name) 改变线程名称
- 创建一个带参数的构造函数,参数传递线程的名字;调用父类的带参数构造方法
- Thread(String name)
Thread类中常用方法
public class Thread{
public Thread();
public Thread(String name);
public Thread(Runnable target);
public Thread(Runnable target,String name);
public String getName();
public void start(); //外部调用
public void run(); // 需要重写
public static void sleep(long millis); //暂停指定毫秒数
public static Thread currentThread();
}
创建线程方式二:
-
定义Runnable接口的实现类,并重写该方法的run()方法
-
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象
-
调用Thread对象的start方法启动线程
public class MyRunnable implements Runnable{ @Override public void run(){ for(int i = 0;i < 20;i++){ sout(i); } } } public class test{ public static void main(String[] args){ MyRunnable mr = new MyRunnable(); Thread th = new Thread(mr); th.start(); } }
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享. 但是如果实现了Runable接口的话,则容易实现资源共享.
优势:
- 适合多个相同的程序代码线程共享同一个资源
- 可以避免java中的单继承的局限性
- 增加线程的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类
匿名内部类实现线程的创建
作用:简化代码
public class Demo{
public static void main(String[] args){
new Thread(){
@Override
public void run(){
//动作
}
}.start();
Runnable r = new Runnable(){
@Override
public void run(){
//动作
}
};
new Thread(r).start();
new Thread(
new Runnable(){
@Override
public void run(){
//动作
}
}
).start();
}
}
线程安全
多个线程同时访问共享资源
解决线程安全的方法:
-
使用同步代码块
synchronized(锁对象){ 可能出现线程安全的代码; }
注意:
- 通过代码块中的锁对象,可以使用任意对象
- 但是必须保证多个线程使用的锁对象是同一个
- 作用:把同步代码块锁住,只让一个线程在同步代码块中执行
原理:
- 使用了锁对象,也叫对象监视器
- 线程抢夺cpu执行权,遇到synchronize代码块,如果检查到锁对象,则会获取锁对象,知道代码块代码执行完成.
-
使用同步方法
把访问了共享数据的代码抽取出来,作为一个新的方法,使用synchronized修饰.
锁对象为this.
public synchronized ....{ //共享数据 }
使用静态同步方法
锁对象不是this(静态方法优先于对象), 是本类的class属性.
-
使用Lock
java.util.concurrent.locks.Lock
接口Lock接口的方法:
void lock() 获取锁
void unlock() 释放锁
java.util.concurrent.locks.ReentrantLock implements Lock
使用步骤:
- 在成员位置创建一个ReentrantLock对象
- 在可能出现线程安全的代码前调用lock
- 在可能出现线程安全的代码后调用unlock
线程状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOEQTM1E-1612419431404)(C:\Users\LiTangMM\AppData\Roaming\Typora\typora-user-images\image-20191212095514418.png)]
Object.wait()
Object.notify()
Thread.sleep()
等待唤醒
public class Test {
public static void main(String[] args) {
// 共同锁对象
Object object = new Object();
Producer producer = new Producer(object);
Consumer consumer = new Consumer(object);
consumer.start();
producer.start();
}
}
public class Consumer extends Thread{
private Object lockobj;
@Override
public void run() {
while (true){
synchronized (lockobj){
System.out.println("顾客线程获取锁对象");
System.out.println("顾客:我需要买东西(进入等待状态)");
// 进入等待状态
try {
lockobj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 被唤醒后执行
System.out.println("顾客被唤醒,执行wait后的代码");
System.out.println("顾客:这是我要的东西,谢谢!");
}
}
}
Consumer(Object object){
super();
lockobj = object;
}
}
public class Producer extends Thread{
private Object lockobj;
@Override
public void run() {
while(true){
synchronized (lockobj){
System.out.println("老板获取锁对象,等待5秒");
// 等待5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老板:东西准备好了(唤醒顾客)");
lockobj.notify();
}
// 保证顾客拿到锁
try {
System.out.println("------------");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Producer(Object object){
super();
lockobj = object;
}
}
等待唤醒机制
线程间通信
等待唤醒机制
如果能获取锁,线程就从WAITING状态变成RUNNABLE状态
否则,从wait set出来,又进入entry set,线程进入阻塞状态.
调用wait和notify方法注意:
- wait方法和notify方法必须要由同一个锁对象调用. 因为: 对应的锁对象可以通过notify唤醒使用同一个锁对象调用wait方法后的线程
- wait方法与notify方法是属于Object类的方法. 因为: 锁对象可以是任意对象, 而任意对象的所属类都是继承了Object类的.
- wait方法与notify方法必须要在同步代码块或者同步函数中使用,因为: 必须要通过锁对象调用这2个方法.
线程池
底层实现LinedList<Thread>
创建线程池:
java.util.concurrent.Executors
static ExecutorService newFixedThradPoll(int nThreads)
返回一个接口对象,通过调用接口方法获取线程
sumbit(Runnable task)
: 提交一个Runnable
任务用于执行
shutdown()
: 关闭/销毁线程池
使用步骤:
- 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务
- 调用ExecutorService中的submint,传递线程任务,开启线程
9. Lambda表达式
lambda表达式的标准格式
由三部分组成:
1. 一些参数
2. 箭头
3. 一行代码
格式:
(参数列表) -> (一些重写方法的代码)
解释说明:
():接口中抽象方法的参数列表
->: 传递的意思
{}: 重写接口的抽象方法的方法体
lambda简写
10. File
文件和目录路径的抽象表示形式
File类的两个静态变量
public class File{
static String pathSeparator; //路径分割符 win; linux:
static String separator; //文件名称分隔符 win\ linux/
}
绝对路径 相对路径
File类的构造方法
public class File{
public File(String pathstring);
// 只封装File对象,不考虑路径的真假情况
public File(String parent, String child);
// parent:父路径 child:子路径
public File(File parent, String child);
// parent为File类,可使用File的方法对路径进行一些操作
}
File类常用方法–获取
public class File{ // 文件 文件夹
public String getAbsolutePath(); // 绝对路径 绝对路径
public String getPath(); // 构造路径 构造路径
public String getName(); // 文件名称 目录名称
public long lenght(); // 文件大小(B) 目录大小(0B)
}
File类常用方法–判断
public class File{
public boolean exits();
public boolean isDirectory();
public boolean isFile();
}
File类常用方法–创建删除
public class File{
public boolean createNeaFile();
public boolean delete();
public boolean mkdir();
public boolean mkdirs();
}
目录遍历
public class File{
public String[] list();
public File[] listFiles():
}