黑马程序员Java零基础视频教程_下部(Java入门,含斯坦福大学练习题+力扣算



Map加入集合也是无序的
put方法的作用添加和覆盖
- 如果在添加数据的时候,键值不存在,则会直接添加到Map集合中,此时返回值是null。
- 如果键值存在,则直接覆盖,但是会返回原有的键值的值。
remove方法不能单独删除value
Map集合第一种遍历方式(健找值):
将Map集合中的key单独拿出来,放到set集合中。利用的是keySet方法,返回值就是一个Set集合。然后再用Iterator,增强for或者lambda来遍历就行。然后利用get方法获取遍历到的key中value的值。
Map集合第二种遍历方式(键值对):

这里涉及到泛型嵌套,既泛型中再去指定泛型的泛型。
利用entrySet方法,将Map集合保存为一个Set集合,但是这次Set和的泛型指定为Map.Entry类型。
再用Iterator、增强for、lambda表达式遍历,利用getKey与getValue方法分别得到键值对的值。
lambda表达式的底层其实是增强for循环,


HashMap的底层原理与HashSet一样,也是hash表的形式(数组+链表+红黑树)。
但是HashMap只比较健的hash值,只保证健的唯一

LinkedHashMap



hashMap默认的空参构造方法,只是加载加载因子用的
HashMap刚创建的时候,数组啥的是不存在的,只有当添加元素的时候才会建立 。


触发扩容机制之后,会将原有的数据移动到或者复制到新的数组中
要存入的元素,先计算出hash值,再与数组长度进行与运算,就是当前元素应存入的位置,如果当前位置没有元素,就是数组当前位置为null,则直接存入数组中。

当添加的元素hash值与当前数组中的元素相同时


当hash值相同,并且key值也相同时,会执行覆盖
hash值相同,key值不同,还是回挂在链表的下面

...:表示可变参数;底层是一个数组
且一个方法的形参中只能由一个可变参数;
如果参数列表中除了可变参数,还有其他类型的参数。则可变参数要写在最后,不然方法调用时识别不出,其他类型变量的值。

Collections

addAll返回值是collection只能给单列集合批量添加

// copy方法不能直接创建集合进行复制,而是先将新建的集合批量加入,并指定长度之后才能复制 // 否则会下标越界
shift+F6:批量改名



双列集合不能直接使用stream流,要先通过entrySet等转为单列集合。


对stream流中数据进行修改,只会影响流中的数据,对原来集合或者数组中的数据没有影响。
concat方法:
当合并两个流时,如果两个流的数据类型不一致,则会选择两个流数据类型共同的父类作为流输出。


收集器如果是Collector.toList()则,集合里面有重复数据也会保留;如果是Collector.toSet则,集合里面不会有重复的数据

如果将集合收集到map里面,需要指定键的规则和值的规则,而且键要保证不能重复。
Lambda表达式的方式书写键值规则:

new Function(键的规则(l流当中数据的类型,键的类型),值的规则(l流当中数据的类型,值的类型))
将集合中的数据封装到对象中,组成对象集合。
stream().map()中,map是用来进行类型转换的。


异常:程序中可能出现的问题


编译时异常:java文件通过javac进行编译时出现的异常
作用在于提醒程序员检查本地信息,如日期解析异常。
与运行时异常:字节码运行时出现的异常,代码出错出现的异常


invoke:调用
异常的作用:
1.用来查询bug的关键参考信息;
2.作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况。

此时,调用者可以选择自己在后台进行处理,或者打印到控制台。
如果程序出现异常,那么虚拟机就会创建一个相应异常的对象,如果此时使用try-catch去捕获这个异常,则会执行catch中的代码。当catch里面的代码执行完毕,就继续执行try-catch下面其他的代码。
如果要捕获多个异常,那么他们共同的父类也要捕获的话,要写在子异常的下面。
如果try中的一行代码出了问题,那么这行代码以下的代码都不会执行。直接进行捕获或者JVM去创建异常对象


System.err.println(要打印的语句);
将要打印的语句以红色字体打印在控制台

throws:写在方法上
throw new:写在方法里,并且结束方法运行

运行时异常用throws在方法上进行抛出时,可以省略不写。
抛出:方法将产生的异常传给调用者。
捕获:方法调用者将方法抛出的异常拿到。
自定义异常类,包括一个无参的构造方法,一个有参的message提示方法
其中,如果是运行时异常,要继承RuntimeException
编译时异常要继承Exception
这样,在catch里面捕获就行
public class 异常类名{
public 异常类构造方法(){
}
public 异常类构造方法(参数列表){
}
}

IO流

程序在读写IO流中的数据

输出流:程序->文件
输入流:文件->程序



文件输出流,就是把程序中的数据写入到文件
步骤:
1.创建文件输出流对象
既通过 FileOutputStream fos = new FileOutputStream("指定文件存放的路径");建立程序与文件的传输通道。
2.写数据
写入数据就相当于数据在这条通道上进行行驶,再到终点。
3.释放资源
就是将这条通道关闭。
FileOutputStream
创建输出流对象,参数可以是用字符串表示的路径,或者new一个File对象再把路径给File对象也可以。
如果指定路径中的文件不存在,则会创建新文件,但是要保证这个文件所在的文件夹存在,不像Linux一样可以多级创建。
如果文件已经存在,则会覆盖文件内容。


FileOutputStream底层,在创建对象的时候会new一个File对象和一个boolean的append参数,append默认时false(也就是默认是覆盖的),改为true之后就不会覆盖,而是续写了。
这样再次执行程序写入时,文件中的内容就不会被覆盖了。
FileInputStream
1.创建字节输入流对象
FileInputStream fis = new FileInputStream("文件路径");
2.读取数据
int i = fis.read();
返回的是一个int型的变量,可以强转成char
read方法更像是有个指针,一次读一个
3.释放资源
fis.close();





GBK字母存储

GBK汉字的存储

一个字节不够,三个字节太多,所以用两个字节就可以,可以标识65535个汉字;为了与英文区分开。


UTF-8:可变字符长度(1-4字节),其中中文用三个字节表示;ASCII用一个字节。是unicode字符集的编码方式


解码时,是先将前面的固定格式去除,再去拼接为汉字就是两个字节的二进制位。再去查询Unicode编码表。

如果一开始用Unicode去编码,然后再用GBK去解码,此时解码时查询的是GBK编码表,GBK用两个字节表示一个汉字,而Unicode用三个字节,所以读完两个字节之后剩下的一个字节就不读了,所以一个汉字会出现读不全的问题。
字符流


除了对象名不同,其他跟字节流差不多


FileReader fr = new FileReader("D:\\My Work National\\IJ_IDEA\\Day05\\a.txt"); int ch = 0; while ((ch = fr.read()) != -1){ System.out.print((char)ch); } fr.close();
java中的换行其实是两个转义字符\r\n


字符流底层原理

字符流有缓冲区,字节流没有

缓冲流

缓冲流只是一个中间态的东西,还是要关联四大基本流来进行操作
释放资源的时候,缓冲流在底层会先释放基本流资源。不需要自己关。

基本流会先读取数据,放入缓冲区当中;
变量b会去读缓冲区中的数据,再把读到的数据放入当,输出流的缓冲区当中。

newLine方法底层会去判断使用的操作系统是什么样的,然后输出相应的换行。

转换流
字节流转换为字符流
通常用于字节流转字符流之后,用字符流里面的方法。
多线程
提高程序的运行效率。
应用场景:聊天软件,拷贝、迁移大文件,加载大量资源文件,后台服务程序。

并发:同一时刻,多个指令在单个CPU上交替执行。
并行:同一时刻,多个指令在多个CPU上同时进行。
并发和并行

线程的启动,new对象,用对象的引用去调用start()方法
继承Thread父类
public class MyTread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + "Hello World"); } } } main方法中 /*MyTread m1 = new MyTread(); m1.setName("线程1"); m1.start(); MyTread m2 = new MyTread(); m2.setName("线程2"); m2.start();*/
实现Runable接口
public class MyRunable implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { //Thread.currentThread.getName 获取当前线程的线程名 System.out.println(Thread.currentThread().getName()+"你好!"); } } } //main方法中 //创建对象 MyRunable myRunable = new MyRunable(); //创建线程对象 Thread t1 = new Thread(myRunable,"线程1"); t1.start(); Thread t2 = new Thread(myRunable,"线程2"); t2.start();
Callable接口如果指定泛型,则call方法的返回值就是这个泛型的返回类型。如果不指定默认是Object类型。
public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; } }


JVM虚拟机在启动的时候,会默认创建多条线程。
其中一条就是main线程,作用是调用main方法,执行里面的代码
关于优先级:
不指定优先级默认就是5
优先级的取值范围:1~10,越大优先级越高
守护线程
当把一个线程定义为守护线程之后,当其他非守护线程结束之后守护线程也会陆续结束,但不是立马,因为JVM跟线程之间也有通信时间。
设置一个程序为守护进程:
Thread t1 = new Thread(程序,设置程序名);
t1.setDaemon(true);
所谓守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。 因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。
如果其他线程的优先级太小,会导致设置的守护进程先执行完了,而其他非守护进程还在执行。
线程的生命周期

线程在就绪状态,会不断地跟其他线程抢夺执行权,当抢夺到之后,程序进入运行状态,期间如果被sleep,则会进入阻塞状态,直到sleep的时间到了,会进入就绪继续上面的步骤,直到run方法中的代码执行完。

锁对象是唯一的,什么都行,Object的也行,但是必须是唯一的
唯一:一把锁对应一道门
public class PayTicket implements Runnable{ static private int ticket = 1; static Object o = new Object(); @Override public void run() { while (true){ //这个锁一般定义为当前类的字节码文件。 synchronized (PayTicket.class) { if (ticket <= 100) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); } else { break; } ticket++; } } } }

线程同步四种方式:
- 一、通过Object的wait和notify
- 二、通过Condition的awiat和signal
- 三、通过一个阻塞队列
- 四、通过两个阻塞队列
StringBuffer和StringBuilder的区别:
StringBuffer是线程安全的,因为每个方法都加了sychronized同步锁。
如果程序是单线程的,不需要考虑数据安全性问题,则可以用StringBuilder;
如果是多线程的,需要考虑数据的安全,则可以用StringBuffer。

Lock锁

public void run() { //synchronized (PayTicketWithThread.class){ while (true) { lock.lock(); if (ticket == 100) { break; } else { ticket++; System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); } //如果unlock释放锁加在这里,则当ticket==100成立时,直接break跳出程序,没有执行释放锁的操作 lock.unlock(); } //} }



public class Disk { /** * * 用于控制生产者和消费者的执行 * * */ //是否有面条;有:1;没有:0;默认:0 public static int foodFlag = 0; //面条总数 public static int count = 10; //锁对象 public static Object lock = new Object(); }
public class Foodie extends Thread{ @Override public void run() { //先写循环 while(true){ synchronized (Disk.lock){ if (Disk.count == 0){ break; }else { //判断是否有面条,0表示没有则去等待 if (Disk.foodFlag == 0){ try { Disk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { //面条-1 Disk.count--; System.out.println("还有"+Disk.count+"碗"); //吃完之后唤醒厨师 Disk.lock.notify(); //修改面条改为没有0 Disk.foodFlag = 0; } } } } } }
public class Cook extends Thread{ @Override public void run() { while (true){ synchronized (Disk.lock){ if (Disk.count == 0){ break; }else { if (Disk.foodFlag == 1){ try { //如果桌上有面条,则lock要等待 Disk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { //如果没有,则生产一碗面条 Disk.foodFlag = 1; //唤醒消费者去吃 Disk.lock.notify(); } } } } } }
Cook c = new Cook(); Foodie f = new Foodie(); c.setName("厨师"); f.setName("吃货"); c.start(); f.start();

线程池

将跟连wifi一样,指定最大连接数


//获取线程池对象
ExcutorService pool = Excutors.newCachedThreadPool(可以指定线程的数量);
//提交任务
pool.submit(new 类名);
//关闭资源
pool.shutdown

这时,cpu会创建临时线程处理任务7和任务8

此时任务10会拒绝服务

主要是第一个拒绝策略。
