File类
-
常用方法
getName(); isDirectory(); isFile(); exists(); listFiles(); createNewFile(); mkdir(); mkdirs(); getPath(); getAbsolutePath(); delete(); deleteOnExit(); renameTo(); getParentFile();
IO流
-
概述:为了在程序中做数据存储的持久化,按流向可以分为输入流跟输出流,按传输的数据单位可以分为字节流与字符流。
-
四大基本流:字节输入流,字节输出流,字符输入流,字符输出流
-
无论哪种流,一定要关闭资源,否则会造成阻塞,磁盘文件会被占用,不能删除也不能修改。
-
字节流与字符流的选择
- 二进制文本,图片,视频,音频必须使用字节流
- 纯文本文件使用字符流
- 不清楚文件类型使用字节流
-
flush(刷新操作)
- 原因:为了提高写入与读取效率,设置一个缓冲区,当缓冲区的数据达到特定值之后,再将其写入磁盘
- 意义
- 可以大大提高cpu的使用率。
- 可以通过操作缓冲区回滚数据。
- 操作系统使用-1代表磁盘文件结束的标志。
- IO是最影响程序性能的,缓冲区设置容量的整数倍,可以有效提高IO性能。
-
try()catch代码块自动释放文件资源
try(FileInputStream in = new FileInputStream(file);//需要关闭的流卸载try代码块之中。 FileOutputStream out = new FileOutputStream(file2)){ byte[] bytes = new byte[1024]; int len; while ((len = in.read(bytes)) != -1) { out.write(bytes, 0, len); } }catch(IOException e){ e.printStackTrace(); }
缓冲流
- 其实就是个包装流,提供缓冲区,提高IO效率
- 字节缓冲流
- 输入:BufferedInputStream
- 输出:BufferedOutputStream
- 字符缓冲流
- 输入:BufferedReader
- 输出:BufferedWriter
转换流
-
定义:把字节流转换成字符流
- InputStreamReader
- OutputStreamWriter
-
InputStreamReader isr = new InputStreamReader(fis, "utf-8")
合并流
-
把多个输入流,合并为一个流对象
-
SequenceInputStream sis = new SequenceInputStream(fis, fis2);
对象流
- 序列化以及反序列化
- 序列化:把内存中的java对象存储到磁盘中的过程,或者给其他的网络节点使用
- 反序列化:把磁盘文件的数据恢复成内存中的java对象
- 为什么要使用序列化
- 数据的共享需要实现序列化(redis)
- 服务的钝化:把不常用的对象存储到磁盘,来节省内存的开销
- 使用json传输的时候
- 要求:必须要实现序列化接口
- 常见问题
- 没有实现序列化接口
- 序列号版本对不上,需要手动引入版本号,避免属性改变,不兼容问题
打印流
- 用来打印数据,打印流只能是输出流
- PrintStream
- PrintWriter
- 拓展点:打印流中的格式化输出:printf();
扫描器类Scanner
-
java.util.Scanner:扫描器类,做输入操作的
hasNextXXXX();//判断有没有这个类型的数据 xxx nextXXX();//得到下个类型的数据
数据流
-
提供了可以读写任何数据的方法
- DataInputStream:提供了readXXX的方法
- DataOutputStream:提供了writeXXX的方法
-
操作
-
读取
DataInputStream dis = new DataInputStream(new FileInputStream(file)); System.out.println(dis.readInt()); System.out.println(dis.readUTF()); dis.close();
-
写入
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); dos.writeInt(123342234); dos.writeUTF("上云老师教的真好呀!"); dos.close();
-
Properties
- 配置文件,资源文件以.properties作为拓展名,使用key_value模式存储数据
- 以后会使用ClassLoader来读取(反射的时候补充)
NIO
-
jdk1.4之后提供的,可以把一块磁盘文件映射到内存中去,java.nio下面的
-
应用领域:一般应用在云服务器中
-
jdk1.7也提供了一个工具包, java.nio.Files
long length = Files.copy(new FileInputStream("E:\\ideaProject\\java核心技术\\基础\\src\\day08\\NIO\\a.txt"), Paths.get("E:\\ideaProject\\java核心技术\\基础\\src\\day08\\NIO\\b.txt")); System.out.println(length);//length:文件的字节数,
-
与io有何不同
- Channels Buffers(通道和缓存), 使用通道和缓冲区进行操作,性能更快
- 异步的,线程从通道读取数据到缓冲区的时候,线程还可以做其他事情
- Selectors(选择器):可以监听多个通道的事件
反射
类加载机制
-
jvm和类的关系
- 运行带有main方法的类(主方法),会启动JVM进程,并加载类的字节码文件,jvm所有线程以及变量都处于同一进程中。
- JVM何时退出:
- 程序正常运行结束
- 出现异常(没有捕获)
- System.exit();方法
- 强制杀死进程
- JVM进程一旦结束,进程中内存的数据会丢失
-
类加载机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-INx6Bwyk-1656170873053)(C:\Users\cc\AppData\Roaming\Typora\typora-user-images\image-20220619182651831.png)]
-
类加载初始化具体步骤(加载,连接,初始化)
- 类的加载
- 类加载器(ClassLoader, 由JVM提供,或者自定义): 将字节码文件(class文件)加载进内存,并且创建一个class对象(java.lang.class)
- 类的连接:(类加载进内存后,生成class对象,把类的二进制数据合并到JRE中)
- 验证:检测被加载的类是否有正确的内部结构
- 准备:负责为类的static变量分配内存空间,设置默认值(并不是初始化操作)
- 解析:把类的二进制数据中的符号引用替换为直接引用(深入分析JVM)
- 类的初始化(jvm负责对类进行初始化,主要对static变量进行初始化)
- 如果该类未被加载和连接,则程序先加载并连接该类,类还可以动态加载(groovy)
- 如果该类的父类未被初始化,则优先初始化父类
- 如果该类有初始化语句(静态代码块),系统依次执行这些语句
- 类的加载
反射的概述
- 问题:Object obj = new String(“abc”);
- 需求:如何调用String中的length方法
- 使用强制类型转换
- 需求:如何调用String中的length方法
- Class对象(表示所有的类)
- Constructor:表示所有的构造器
- Method:表示所有的方法
- Field:表示所有的字段
- 通过反射,得到类的元数据信息(构造器,方法,字段,内部类)
- 当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,
Class类和Class实例
-
Class类表示所有的类
-
Class实例表示一份份的字节码(类, 接口,注释)
-
String类如何表示:
- String Class<java.lang.String>
- HashMap Class<java.util.HashMap>
-
获取类的实例
- 使用类名.class:String.class
- 使用Class的静态方法:Class.forName(“类的全类名”)
- 使用对象.getClass()方法:对象.getClass();
-
同一个JVM中,只存在一个类的一份字节码和一个字节码对象
-
基本类型的字节码的表示
-
Class<Integer> integerClass = int.class;//int类型的类实例,jvm提前内置了 Class<Integer> type = Integer.TYPE;//包装类型提供了一个type常量,也是可以获取基本类型的类实例
-
证明Integer和int不是同一种类型
System.out.println(Integer.class == int.class); System.out.println(Integer.class == Integer.TYPE);
-
反射操作构造器
-
Class去操作Constructor类
-
常用的方法
//获取构造器对象
public Constructor<T> getConstructor(Class<?>... parameterTypes)
//获取全部构造器对象
public Constructor<?>[] getConstructors()
//获取私有的构造器
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
- 获取到的私有构造器不能直接创建对象,需要setAccessible(true);
反射操作对象的方法
-
获取静态方法
//获取静态对象,调用方法不需要传入对象 Method add = c.getMethod("add", Student.class); add.invoke(null, new Student());
使用类加载器加载Properties
-
从当前项目的根目录下开始找(使用文件输入流)
Properties pro = new Properties(); pro.load(new FileInputStream("基础/src/day08/propertiesDemo/a.properties"));
-
从src目录下开始找(使用当前线程的类就在其)
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); InputStream is = contextClassLoader.getResourceAsStream("day08/propertiesDemo/a.properties");
-
从当前类的目录下开始找(使用当前类的类加载器)
InputStream is = PropertiesDemo3.class.getResourceAsStream("a.properties");
多线程
并行和并发
- 并行:多件事情在同一时刻同时发生
- 并发:多件事情在同一时间段同时发生
- 单核cpu:一个时刻只能执行一个程序,多个程序可以交替执行。
- 线程调度:多个线程以某个顺序执行
进程和线程
- 进程:内存中的正在运行的程序,在内存中分配一块空间(独立的),一个应用程序可以同时启用多个进程,至少一个线程
- 线程:进程中的执行单元,一个进程可以有多个线程,栈空间独立,堆空间共享
- 多线程的优势:因为堆空间共享,所以内存空间开销较少。
- 多线程具体运行哪个线程,取决于cpu的调度
- java中至少有几个线程
- 主线程
- GC垃圾回收线程
- 线程调度:JVM采用抢占式的线程调度,没有分时执行的概念,谁抢到谁执行
- 应用:抢票,游戏,多线程下载
多线程的实现方式
-
继承Thread类
MyThread myThread = new MyThread();
-
实现Runnable接口
Thread thread = new Thread(new MyRunable());
线程的调度
-
public static native void sleep(long millis)//线程睡眠 public final synchronized void join(long millis)//阻塞调用join方法的线程,让我执行完毕,再恢复
-
守护线程:GC的使用
- 功能:为其它线程服务
- 特点:主线程结束,守护线程就结束
- 创建线程:默认是前台线程
- 获取守护线程:Thread.setDaemon(true),在start方法执行之前设置
-
获取当前线程及其名称
Thread currentThread = Thread.currentThread(); System.out.println(currentThread.getName());
-
线程设置优先级
- 注意:优先级高,只是获取执行机会更大(取决于cpu调度)
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmNYWWNE-1656170873054)(C:\Users\cc\AppData\Roaming\Typora\typora-user-images\image-20220619230439833.png)]
-
线程的让位:yield();
- 当前线程向cpu调度器表示愿意让出cpu资源(但是调度器可以忽略)
- sleep和yield的区别:
- 都能使得当前处于运行状态的线程暂时放弃cpu,把机会留给其他线程
- sleep不考虑其他线程的优先级,给其他线程运行机会,yield只会给同等优先级或优先级比自己高的运行机会
- 调用sleep方法后,线程进入等待状态,调用yield方法后线程进入就绪状态
线程的同步操作
-
同步方法(synchronized修饰方法)
-
同步代码块
- synchronized(对象锁)
- synchronized(class对象锁)
-
锁对象
Lock lock = new ReentrantLock(); lock.lock(); lock.unlock();
生产者与消费者案例分析
- 生产者(多个线程)与消费者(多个线程)共享数据
- 解决生产者和消费者的数据紊乱问题
- 只要保证生产产品的过程中,不允许消费者操作产品
- 同步方法/同步代码块/锁
- 应该先生产一个数据,然后再消费一个数据
- 生产者生产完一个数据,唤醒消费者,然后进入等待
- 消费者醒了后,开始消费,然后唤醒生产者,进入等待
- 使用等待唤醒机制
等待和唤醒机制
-
线程通信(wait)和(notify)方法介绍
- wait(); 执行该方法的线程对象要释放同步锁,并把该线程放到等待池中去等待
- notify(); 执行该方法的线程对象唤醒等待池的线程,并将其添加到锁池中去等待
- notifyAll(); 执行该方法的线程对象唤醒等待池中的所有线程,把这些线程都转移到锁池中去等待
- 注意:这些方法只能被同步监听对象锁(同步锁)调用
-
同步监听锁对象(同步锁):多个线程有公共对象使用时,多个线程之间才会出现互斥现象,共同的对象就叫做同步监听锁对象
-
同步锁池:同步锁必须选择多个线程共有的资源对象,当生产者生产数据时(先获取锁),生产者没生产出来的时候,消费者只能在锁池等待,当生产者生产完后,释放同步锁,消费者可以抢锁来进行消费。
-
Lock和Condition接口
-
Lock机制根本没有同步锁,也没有释放锁的逻辑
-
使用Condition接口对象中的await, signal, signalAll方法取代Object中的wait, notify, notifyAll
private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition();
-
-
建议使用第二种方式:更加灵活
死锁
- 多线程通信中,很容易出现死锁现象,死锁是不能解决的,只能避免
- 如P线程等待C线程持有的锁,C线程等待P线程持有的锁,JVM不检测也不能避免这种情况
- Thread类中的**suspend()和resume()**方法很容易导致死锁,不要去使用。
- suspend():是正在运行的线程暂时放弃cpu,但是不释放锁。
- 如何避免死锁:当线程A、B、C去访问共享资源的时候,保证每一个线程都按照相同的顺序访问。
- 上锁之后记得释放锁,如果有finally代码块,将释放锁的操作放到finally代码块中
线程的生命周期
-
线程状态
- NEW:还未开启的线程状态
- RUNNABLE:正在运行中的线程状态,也可能在等待其他资源比如(处理器)
- ready:等待cpu的调度
- running:正在运行中
- TIMED_WAITING(计时等待):调用Tread.sleep(long),Object.wait(long), Thread.join(long)会进入该状态
- WAITING(等待的状态):只能被其他线程唤醒
- 等待中的线程,由于调用了Object的wait()方法,或者Thread的join()方法
- 运行中的线程调用了wait()方法,此时JVM会将其放在等待池中等待唤醒
- BLOCKED(阻塞的状态):运行中的线程,由于某些原因暂时放弃了cpu,此时jvm不会为线程分配cpu
- 线程处于运行状态,但没有获取到同步锁时,JVM会把该线程放到同步锁对象的锁池中去,该线程阻塞
- 线程运行中发出IO请求,JVM会将该线程阻塞
- TERMINATED(死亡的状态):
- 线程结束了
- 出现异常
定时器与线程组
-
定时器的使用
timer.schedule(new TimerTask() { @Override public void run() { System.out.println("================"); } }, 10000L, 2000L);
线程组
- 可以对线程集中管理,ThreadGroup
- 用户创建线程时,可以指定线程组
注解
- java注解:jdk1.5开始引入,也称为java标记,可以通过反射获取标记,编译器在编译java文件时,也会把java注解编译到字节码文件中去
- 注解的使用场景
JAVA8特性
-
Lambda表达式:允许一个函数作为参数传递到方法中去。
-
stream(数据源的元素队列,支持聚合操作, 想象成流)
- 数据源:集合,数组
- 聚合操作:filter(过滤), map(映射),limit(截取指定数量),reduce,find, match, sorted(排序), forEach(循环)
-
接口中可以提供静态方法和默认方法
-
新的dataAPI
-
旧版存在问题
- 设计较差(Java.util, java.sql包都有
- 非线程安全的
- 处理时区比较麻烦
-
新版提供
-
Local(本地):简化了事件处理,没有了时区的问题
-
Zoned(时区):指定时区处理时间
LocalDateTime time = LocalDateTime.now(); System.out.println(time); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime parse = LocalDateTime.parse("1999-07-20 12:00:45", formatter); System.out.println(parse); ZoneId zoneId = ZoneId.systemDefault(); System.out.println(zoneId);
-
-
网络编程
IP
-
获取ip地址
InetAddress ip = InetAddress.getByName("DESKTOP-QQ9QB67"); System.out.println(ip.getHostName()); System.out.println(ip.getHostAddress()); //判断是否可以连接 System.out.println(ip.isReachable(1000));
端口
- 设备和外部设备通信交流的出口
- 常用端口:80, 8080, 3306, 6379
- 注意:同一台电脑不能同时出现同一个端口,否则端口冲突
协议
- 通信双方必须遵守的规则
- http:超文本传输协议(免费)
- https:使用安全的套接字传输的超文本协议(收费)
- ftp:文件传输协议
- file:本机电脑或者网上分享的协议
URI与URL
- uri:统一资源标识符
- 表示某一个互联网资源的字符串:包含主机名,端口,标识符
- url:统一资源定位符
- 是互联网的标准资源地址
传输层协议
-
TCP:面向连接(三次握手),传输可靠(保证数据的正确性和数据顺序正确性),建立连接开销大,用来传输数据量大的,速度慢
-
UDP:面向非连接,传输不可靠,用于传输少量数据(数据报包模式)、速度快,用于发送端和接收端
-
实操TCP
//建立一个服务器套接字 ServerSocket server = new ServerSocket(9999); System.out.println("服务器准备就绪"); //与客户端建立连接 Socket accept = server.accept(); OutputStream outputStream = accept.getOutputStream(); PrintStream ps = new PrintStream(outputStream); ps.print("ip地址是:"+accept.getInetAddress()); ps.print("连接服务器成功!"); ps.close(); server.close(); Socket socket = new Socket("127.0.0.1", 9999); InputStream is = socket.getInputStream(); Scanner scanner = new Scanner(is); while (scanner.hasNext()){ System.out.println(scanner.next()); } scanner.close(); socket.close();
-
实操UDP
String data = "我是发送者"; //创建发送端对象 DatagramSocket socket = new DatagramSocket(9999); DatagramPacket packet = new DatagramPacket(data.getBytes(), data.getBytes().length, InetAddress.getLocalHost(), 1010); socket.send(packet); socket.close(); //建立接收端对象 DatagramSocket socket = new DatagramSocket(1010); byte[] buf = new byte[1024]; DatagramPacket pack = new DatagramPacket(buf, 1024); socket.receive(pack); String s = new String(buf, 0, pack.getLength()); System.out.println(s); socket.close();
String data = "我是发送者"; //创建发送端对象 DatagramSocket socket = new DatagramSocket(9999); DatagramPacket packet = new DatagramPacket(data.getBytes(), data.getBytes().length, InetAddress.getLocalHost(), 1010); socket.send(packet); socket.close(); //建立接收端对象 DatagramSocket socket = new DatagramSocket(1010); byte[] buf = new byte[1024]; DatagramPacket pack = new DatagramPacket(buf, 1024); socket.receive(pack); String s = new String(buf, 0, pack.getLength()); System.out.println(s); socket.close();