java高级编程
一、多线程
多线程的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
何时需要多线程:
1.程序需要同时执行两个或多个任务。
2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3.需要一些后台运行的程序时。
1、程序、进程、线程的理解
1.1程序
1.程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。
2.指一 段静态的代码(没有加载到内存当中,没有cpu参与运算),静态对象。
1.2进程
1.进程(process):正在运行的一个程序。
2.是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
3.说明:使用继承于Thread类的方式,存在线程安全问题,待解决。
1.3线程
1.线程(thread)是一个程序内部的一条执行路径。
2.若一个进程同一时间并行执行多个线程,就是支持多线程的
3.一个Java应用程序java.exe,至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。
4.说明:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
补充:内存结构:
进程可以细化为多个线程。
每个线程,拥有自己独立的:栈、程序计数器。
多个线程,共享同一个进程中的结构:方法区、堆。
2、并行与并发
-
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
-
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
3、线程的创建和使用
3.1多线程的创建方法一:继承于Thread 类
1.Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread 类来体现。
2.步骤:
(1)创建一个继承于Thread类的子类
(2)重写Thread类的run()
(3)创建Thread类的子类的对象
(4)通过此对象调用start()
//(1)创建一个继承于Thread类的子类
class Mythread extends Thread{
//(2)重写Thread类的run()---->将此线程执行的操作声明在run()中
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// (3)创建Thread类的子类的对象
Mythread t1 = new Mythread();
// (4)通过此对象调用start()①启动当前线程②调用当前线程的run()
t1.start();
//如下操作仍然在主线程中执行
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
3.注意
(1)不能通过直接调用run()方法启动线程
(2)不能让已经start()过得线程再次执行。报错:IllegalThreadStateException
需要重新创建一个线程对象。
3.2多线程的创建方法二:实现Runnable接口
1.步骤:
(1)创建一个实现了Runnable接口的类
(2)实现类去实现Runnable中的抽象方法:run()
(3)创建实现类的对象
(4)将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
(5)通过Thread类的对象调用start()
package com.day01.java;
/**
* @description:创建多线程的方式二:实现runnable接口:遍历100以内的偶数
* (1)创建一个实现了Runnable接口的类
* (2)实现类去实现Runnable中的抽象方法:run()
* (3)创建实现类的对象
* (4)将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* (5)通过Thread类的对象调用start()
* @author: liermin
* @time: 2022/11/3 10:25
*/
//(1)创建一个实现了Runnable接口的类
class MThread implements Runnable{
//(2)实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// (3)创建实现类的对象
MThread mThread = new MThread();
// (4)将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
// (5)通过Thread类的对象调用start():启动线程;调用当前线程的run()--->调用了runnable类型的target的run()
t1.start();
//再提供一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.start();
}
}
3.3两种创建线程方式的比较
优先选择实现runnable接口的方式
原因:
(1)没有类的单继承性的局限性
(2)更适合处理有共享数据的情况
联系:public class Thread implements Runnable()
相同点:两种方式都需要重写run(),江县城要执行的逻辑生命在run()中。
4、Thread类中的常用方法
4.1常用方法
- start():启用当前线程;调用当前线程的run()。
- run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。
- currentThread():静态方法,返回执行当前代码的线程。
- getName():获取当前线程的名字。
- setName():设置当前线程的名字。
- yield():释放当前线程。
- join() :在线程A中调用线程B的join()方法,此时线程A进入阻塞状态,直到线程B完全执行结束,线程A结束阻塞状态。
- stop():已过时。当执行此方法时,强制结束当前线程。
- sleep(long millis):让当前线程“睡眠”指定的millis毫秒,在指定的millis毫秒时间内,当前线程时阻塞状态。
- isAlive():判断当前线程是否存活。
4.2线程的优先级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5—>默认优先级
2.获取和设置当前线程的优先级
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。从概率上看,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完后,低优先级的线程才执行。
补充:线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
5、线程的生命周期
说明:
1.生命周期:状态、方法
2.状态A–>状态B:执行了那些方法(回调方法)
某个方法主动调用:状态A–>状态B
3.阻塞:临时状态 ,不可以作为最终状态
死亡:最终状态。
6、线程的同步
1.背景:
例子:三个窗口同时卖票,总票数为100张
1)问题:买票过程中,出现重票、错票—>出现了线程安全问题
2)问题出现的原因:当某个线程操作车票的过程中,操作尚未完成时,其他线程参与进来操作车票。
3)解决方法:当一个线程A操作票时,其他线程不能参与进来,直到线程A操作完成,其他线程才可以开始操作。即使线程A出现阻塞,也不能被改变。
2.java解决线程的安全问题:同步机制
(1)方法一:同步代码块
synchronized(同步监视器){
// 需要被同步的代码
}
说明:
①操作共享数据的代码即为需要被同步的代码
②共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
③同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程公用同一把锁。
补充:
-
在实现runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。
-
在继承Thread类创建多线程的方式中,谨慎使用this充当同步监视器。考虑使用当前类充当同步监视器。
(2)方法二:同步方法
操作共享数据的代码完整的生命在一个方法中,可以将此方法声明为同步的。
总结:
①同步方法仍涉及同步监视器,但不需要显示的声明
②非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
(3)方式三:Lock锁----->JDK5.0新增
面试题:synchronized 与lock的异同
相同:解决线程安全问题
不同:synchronized机制在执行完相应的同步代码之后,自动的释放同步监视器;
lock需要手动的启动同步(lock),同时结束永不也需要手动的实现(unlock)
(4)使用的优先顺序:Lock—> 同步代码块 —> 同步方法
3.利弊
(1)同步的方式,解决了线程的安全问题—>好处
(2)操作同步代码时,只能有一个线程参与,其他线程需等待。相当于是一个单线程的过程,效率低。—>局限性
6.1线程安全的单例模式(懒汉式)
package com.day02.java1;
//使用同步机制将单例模式中的懒汉式改写为线程安全的
public class BankTest {
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance() {
// 方式一:效率差
// synchronized (Bank.class) {
// if (instance == null) {
// instance = new Bank();
// }
// return instance;
// }
// 方式二
if (instance == null) {
synchronized (Bank.class){
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
6.2死锁
1.死锁的理解:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2.说明:
(1)出现死锁后,不会出现异常,不会出现提示,但是所有的线程都处于阻塞状态,无法继续
(2)使用同步时,避免出现死锁
package com.day02.java1;
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
7、线程的通信
1.涉及的方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的
notifyAll():一旦执行此方法,唤醒所有被wait的线程。
2.说明:
1.wait()、notify()、notifyAll()只能出现在同步代码块或同步方法中
2.wait()、notify()、notifyAll()的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
3.wait()、notify()、notifyAll()定义在java.long.Object类中
3.面试题:sleep和wait的异同?
相同:执行方法,可以是当前线程进入阻塞状态
不同:
(1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
(2)调用的范围不同:sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或同步方法中
(3)关于是否释放同步监视器:使用在同步代码块或同步方法中时,sleep不会释放锁(同步监视器),wait会释放锁
4.总结
(1)释放锁的操作
- 当前线程的同步方法、同步代码块执行结束。
- 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
(2)不会释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行。
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
- 应尽量避免使用suspend()和resume()来控制线程。
8、JDK5.0新增线程创建方式
8.1多线程新增创建方式一:实现Callable接口。
package com.day02.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
// 2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
// 3.创建callable接口实现类的对象
NumThread numThread = new NumThread();
// 4.将此callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
// 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
// 6.获取callable中call方法的返回值
// get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();
System.out.println("和" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解实现Callable接口的方式创建多线程比实现Runnable接口的方式创建多线程的方式更强大?
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable支持泛型
8.2多线程新增创建方式二:创建线程池。
package com.day02.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0 ){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
// 1.提供指定县城数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// 设置线程池的属性
// System.out.println(service.getClass());
service1.setCorePoolSize(15);
// 2.执行指定的线程的操作。需要提供实现runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合使用于Runnable
// service.submit(Callable callable);//适合适用于Callable
// 3.关闭连接池
service.shutdown();//关闭连接池
}
}
好处:
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理
-
corePoolSize:核心池的大小
-
maximumPoolSize:最大线程数
-
keepAliveTime:线程没有任务时最多保持多长时间后会终止
-
二、java常用类
1、java.lang.String类的使用
1.概述
String:字符串,使用一对""引起来表示
(1)String声明为final的,不可被继承
(2)String实现了Serializable接口:表示字符串支持序列化。
String实现了Comparable接口:表示String可比较大小
(3)String内部定义了final char[] value用于存储字符串数据。表示数组不能被重新赋值,数组的元素不能被修改—>不可变
(4)String:代表不可变的字符序列。简称:不可变性。
(5)通过字面量的方式给字符串赋值,此时字符串值声明在字符串常量池中。
(6)字符串常量池中不会存储相同内容(使用String类的equals()比较,返回true)的字符串。
2.String的不可变性
(1)说明
- 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有字符串进行连接操作时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace()方法修改指定字符或字符串时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
(2)代码举例
public void test1(){
String s1 = "abc";//字面量的定义方式
String s2 = "abc";
s1 = "hello";
System.out.println(s1 == s2);//比较地址值
System.out.println(s1);//hello
System.out.println(s2);//abc
String s3 = "abc";
s3 += "def";
System.out.println(s2);
System.out.println(s3);
String s4 = "abc";
String s5 = s4.replace('a','l');
System.out.println(s4);
System.out.println(s5);
}
3.String实例化的不同方式
(1)方法说明
方式一:通过字面量定义的方式
方式二:通过new + 构造器的方式定义
(2)举例
public void test2(){
// 此时的s1和s2的数据生命在方法区中的字符串常量值中
String s1 = "java";
String s2 = "java";
// 此时的s3和s4保存的地址值,是数据在堆空间开辟空间以后对应的地址值。
String s3 = new String("java");
String s4 = new String("java");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
}
(3)说明:
通过String s = new String(“abc”);的方式创建对象,在内存中创建了2个对象:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
(4)图示
4.字符串拼接方式赋值的对比
(1)说明
- 常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量
- 其中之一是变量,结果在堆中。
- 调用intern()方法,返回值在常量池中
(2)举例
public void test3(){
String s1 = "java";
String s2 = "hadoop";
String s3 = "javahadoop";
String s4 = "java" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "java" + s2;
String s7 = s1 + s2;
// 比较地址值
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
String s8 = s5.intern();//返回值得到的s8是常量池中已经存在的“javahadoop”
System.out.println(s3 == s8);//true
}
5.String常用方法
-
int length():返回字符串的长度: return value.length
-
char charAt(int index): 返回某索引处的字符return value[index]
-
boolean isEmpty():判断是否是空字符串:return value.length == 0
-
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
-
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
-
String trim():返回字符串的副本,忽略前导空白和尾部空白
-
boolean equals(Object obj):比较字符串的内容是否相同
-
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
-
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于“+”
-
int compareTo(String anotherString):比较两个字符串的大小 (涉及到字符串排序)
-
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从 beginIndex开始截取到最后的一个子字符串。
-
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字 符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。(左闭右开)
-
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
-
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
-
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
-
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列 时,返回 true
(1)查找:
-
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
-
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
-
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
-
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
思考:什么情况下,indexOf(str)和lastindexOf(str)返回值相同?情况一:存在唯一一个str;情况二:不存在str。
(2)替换:
-
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
-
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
-
String replaceAll(String regex, String replacement) : 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
-
String replaceFirst(String regex, String replacement) : 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
(3)匹配:
-
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
String str = "12345"; //判断str字符串中是否全部有数字组成,即有1-n个数字组成 boolean matches = str.matches("\\d+"); System.out.println(matches); String tel = "0571-4534289"; //判断这是否是一个杭州的固定电话 boolean result = tel.matches("0571-\\d{7,8}"); System.out.println(result);
(4)切片:
-
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
-
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
6.String与基本数据类型转换
(1)String 与基本数据类型、包装类之间的转换:
String---->基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类---->String:调用String重载的valueOf(xxx)
(2)String 与字符数组(char[])之间的转换:
String---->char[]:调用String的toCharArray()方法
char[]---->String:调用String的构造器
public void test4(){
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{'h','e','l','l','o'};
String str2 = new String(arr);
System.out.println(str2);
}
(3)String 与字节数组(byte[])之间的转换:
String---->byte[]:调用String的getBytes()方法—编码
byte[]---->String:调用String的构造器—解码
编码:字符串—>字节(看得懂—>看不懂的二进制数据)
解码:字节—>字符串(编码的逆过程)(看不懂的二进制数据—>看得懂)
说明:解码时,要求使用的字符集必须与编码时使用的字符集一致,否则出现乱码。
public void test4() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集:utf-8,进行编码
System.out.println(Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");
System.out.println(Arrays.toString(gbks));
String str2 = new String(bytes);//使用默认的字符集:utf-8,进行解码
System.out.println(str2);
String str3 = new String(gbks);
System.out.println(str3);//出现乱码。原因:编码集和解码集不一致。
String str4 = new String(gbks,"gbk");
System.out.println(str4);//不会出现乱码。原因:编码集和解码集一致。
}
(4)String 与StringBuffer、StringBuilder之间的转换:
String—>StringBuffer、StringBuilder:调用StringBuffer、StringBuilder构造器
StringBuffer、StringBuilder—>String:①调用String构造器②StringBuffer、StringBuilder的toString()
7.JVM中字符串常量池存放的位置
jdk1.6:字符串常量池存储在方法区(永久区)
jdk1.7:字符串常量池存储在堆空间
jdk1.8:字符串常量池存储在方法区(元空间)
2、StringBuffer、StringBuilder
1.String、StringBuffer和StringBuilder的异同
String:不可变的字符序列:final修饰;底层使用char[]数组存储
StringBuffer:可变的字符序列:线程安全的,效率低(多线程问题使用StringBuffer);底层使用char[]数组存储
StringBuilder:可变的字符序列:jdk5.0新增,线程不安全,效率高(不涉及多线程或不存在多线程的安全问题时使用StringBuilder);底层使用char[]数组存储
2.StringBuffer、StringBuilder的内存解析
以StringBuffer为例
/*
* 源码分析:
* String str = new String();//char[] value = new char[0];
* String str1 = new String("abc");// char[] value = new char[]{'a','b','c'};
*
* StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组
* System.out.println(sb1.length);//0
* sb1.append('a');//value[0] = 'a';
* sb1.append('b');//value[1] = 'b';
*
* StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
* (1)System.out.println(sb2.length);//3
* (2)扩容问题:添加的数据超过底层数组,需要扩容底层的数组。
* 默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新数组中。
* 建议使用StringBuffet(int capacity)或StringBuilder(int capacity)
*
* */
3.对比String、StringBuffer、StringBuilder三者的执行效率
StringBuilder > StringBuffer > string
4.StringBuffer、StringBuilder中的常用方法
总结:
- 增:append(xxx)
- 删:delete(int start,int end)
- 改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
- 查: charAt(int n )
- 插:insert(int offset, xxx)
- 长度:length()
- 遍历:for + charAt() / toString()
3、JDK8之前的日期时间API
1.获取系统当前时间:System类中的currentTimeMillis()
long time = System,currentTimeMillis();
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差
//称为时间戳
System.out.println(time);
2.java.util.Date类与java.sql.Date类
(1)两个构造器的使用
①Date():创建一个对应当前时间的Date对象
②Date(long millisec):创建指定毫秒数的Date对象
(2)两个方法的使用
①toString():显示当前的年、月、日、时、分、秒
②getTime():获取当前Date对象对应的毫秒数。(时间戳)
(3)java.sql.Date类对应着数据库中的日期类型的变量
①如何实例化:java.sql.Date date = new java.sql.Date(12334444332L);
②如何将java.util.Date 对象转化为java.sql.Date对象
3.java.text.SimpleDateFormat类:SimpleDateFormat对日期Date类的格式化和解析
(1)操作
①格式化:日期—>字符串
②解析:格式化的逆过程,字符串—>日期
(2)SimpleDateFormat的实例化:new+构造器
// 指定方式格式化和解析:带参数的构造器
// SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 格式化
String format1 = sdf1.format(date);
System.out.println(format1);
// 解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),否则抛异常
Date date2 = sdf1.parse("2022-11-16 11:45:02");
System.out.println(date2);
4.java.util.Calendar日历类(抽象类)
(1)实例化
①创建其子类的对象
②调用其静态方法
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getClass());
(2)常用方法
get()/set()/add()
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//set()
//可变性
calendar.set(Calendar.DAY_OF_MONTH,11);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime():日历类-->Date
Date date = calendar.getTime();
System.out.println(date);
//setTime():Date--->日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
4、JDK8中新日期时间API
1.日期时间API的迭代:
第一代:jdk1.0 Date类
第二代:jdk1.1 Calender类,一定程度上替代Date
第三代:jdk1.8 新的一套API
2.前两代存在的问题:
可变性:像日期和时间这样的类应该是不可变的。
偏移性:Date中的年份是从1900开始的,而月份都从0开始。
格式化:格式化只对Date有用,Calendar则不行。
此外,它们也不是线程安全的;不能处理闰秒等。
3.java8中新的日期时间API涉及的包
- java.time – 包含值对象的基础包
- java.time.chrono – 提供对不同的日历系统的访问
- java.time.format – 格式化和解析时间和日期
- java.time.temporal – 包括底层框架和扩展特性
- java.time.zone – 包含时区支持的类
说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽 管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。
4.本地日期、本地时间、本地日期时间的使用LocalDate、LocalTime、LocalDateTime
(1)说明:
①分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。 它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区 相关的信息。
②LocalDateTime相较于LocalDate、LocalTime,使用频率更高
③类似于Calendar
(2)常用方法:
5.时间点:instant
(1)说明:
①时间线上的瞬时点。它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。
②类似于java.util.Date类
(2)常用方法:
6.日期时间格式化类:DateTimeFormatter
(1)说明:
①格式化或解析时间、日期
②类似于SimpleDateFormat
(2)常用方法:
①实例化方式
预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)
自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
@Test
public void test3(){
//方式一:预定义的标准格式。如: ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
System.out.println(localDateTime);
System.out.println(str1);
//解析:字符串--->日期
TemporalAccessor parse = formatter.parse("2022-11-22T19:39:30.556");
System.out.println(parse);
//方式二:本地化相关的格式。如:ofLocalizedDateTime()
// FormatStyle.LONG、FormatStyle.MEDIUM、FormatStyle.SHORT:适用于LocalDateTime
//格式化:日期-->字符串
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
String str2 = formatter1.format(localDateTime);
System.out.println(str2);
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
String str3 = formatter2.format(localDateTime);
System.out.println(str3);
//解析:字符串--->日期
TemporalAccessor parse1 = formatter1.parse("22-11-22 下午7:45");
System.out.println(parse1);
// 本地化相关的格式。如:ofLocalizedDate()
// FormatStyle.FULL、FormatStyle.LONG、FormatStyle.MEDIUM、FormatStyle.SHORT:适用于LocalDate
DateTimeFormatter formatter3 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String str4 = formatter3.format(LocalDate.now());
System.out.println(str4);
//方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter4 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
//格式化
String format = formatter4.format(LocalDateTime.now());
System.out.println(format);
//解析
TemporalAccessor parse2 = formatter4.parse("2000-06-13");
System.out.println(parse2);
}
②常用方法
5、java比较器
1.使用背景:java中的对象,正常情况下,只能进行比较:== 或!=,不能使用>或<,但在开发场景中,需要对多个对象进行排序;言外之意,需要比较对象的大小,使用Comparable或Comparator接口来实现。
2.自然排序:Comparable接口
(1)说明:
①像String、包装类等实现了 Comparable接口,重写了compareTo(obj)方法,给出比较两个对象大小的方法。
②像String、包装类重写compareTo(obj)方法后,进行了从小到大的排序。
③重写compareTo(obj)规则:
- 如果当前对象this大 于形参对象obj,则返回正整数,
- 如果当前对象this小于形参对象obj,则返回 负整数,
- 如果当前对象this等于形参对象obj,则返回零。
④自定义类需要排序,使自定义类实现Comparable接口,重写compareTo(obj)方法;在compareTo(obj)方法中指明如何让排序。
//指明商品排序方式:价格从小到大,价格相同情况下按照产品名称从低到高
@Override
public int compareTo(Object o) {
if (o instanceof Goods){
Goods goods = (Goods) o;
if (this.price > goods.price){
return 1;
}else if (this.price < goods.price){
return -1;
}else {
// return 0;
return this.name.compareTo(goods.name);
}
//方法二:return Double.compare(this.price,goods.price);
}
throw new RuntimeException("数据类型不一致");
}
3.定制排序:Comparator接口
(1)背景
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码, 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那 么可以考虑使用 Comparator 的对象来排序。
(2)重写compare(Object o1 ,Object o2)方法,比较o1、o2大小:
- 如果方法返回正整数,表示o1>o2;
- 如果返回0,表示o1=o2;
- 如果返回负整数,表示o1<o2。
Arrays.sort(arr,new Comparator(){
//先按照名称从低到高,再按照价格从高到低
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Goods && o2 instanceof Goods){
Goods g1 = (Goods) o1;
Goods g2 = (Goods) o2;
if (g1.getName().equals(g2.getName())){
return -Double.compare(g1.getPrice(),g2.getPrice());
}else {
return g1.getName().compareTo(g2.getName());
}
}
throw new RuntimeException("数据类型不一致");
}
});
4.Comparable接口与Comparator 接口的对比:
- Comparable接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
- Comparator 接口属于临时性的比较。
6、其他类
6.1System类(系统类)
- System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。 该类位于java.lang包。
- 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
- 方法:
- native long currentTimeMillis():返回当前的计算机时间
- void exit(int status):退出程序
- void gc():是请求系统进行垃圾回收
- String getProperty(String key):获得系统中属性名为key的属性对应的值
6.2Math类
java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回 值类型一般为double型。
6.3BigInteger与BigDecimal
1.java.math包的BigInteger可以表示不可变的任意精度的整数
2.要求数字精度比较高,故用到java.math.BigDecimal类。
三、枚举类与注解
1、枚举类
1.枚举类的使用
(1)理解:类的对象是有限个、确定的,称此类为枚举类。
(2)定义一组常量时,建议使用枚举类
(3)如果枚举类中只有一个对象,可以作为单例模式的实现方式。
2.枚举类的定义
方式一:jdk5.0之前,自定义枚举类
//自定义枚举类
class Season{
//1.声明Season对象的属性: private final 修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final修饰
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","白雪皑皑");
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4.其他诉求2:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
方式二:jdk5.0,使用enum关键字定义枚举类
说明:定义的枚举类默认继承于java.lang.Enum类
//使用enum定义枚举类
enum Season1{
//1.提供的当前枚举类的对象
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","白雪皑皑");
//2.声明Season对象的属性: private final 修饰
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
3.Enum类中的常用方法
- values()方法:返回所有的枚举类对象构成的数组
- valueOf(String str):返回枚举类中对象名是str的对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常: IllegalArgumentException。
- toString():返回当前枚举类对象常量的名称。
4.使用enum关键字定义的枚举类实现接口的情况
(1)实现接口,在enum类中实现抽象方法
(2)枚举类的对象分别实现接口中的抽象方法
interface Info{
void show();
}
//使用enum定义枚举类
enum Season1 implements Info{
//1.提供的当前枚举类的对象
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","白雪皑皑"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
//2.声明Season对象的属性: private final 修饰
private final String seasonName;
private final String seasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
2、注解(Annotation)的使用
1.理解
(1)jdk5.0新增
(2)Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加 载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员 可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
(3)在JavaSE中,注解的使用目的比较简单,例如标记过时的功能, 忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如 用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
2.Annotation使用实例
示例一:生成文档相关的注解
示例二:在编译时进行格式检查(JDK内置的三个基本注解)
- @Override: 限定重写父类方法, 该注解只能用于方法
- @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为 所修饰的结构危险或存在更好的选择
- @SuppressWarnings: 抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
3.自定义注解:参照@SuppressWarnings定义
(1)步骤
①注解声明为@interface
②内部定义成员通常使用value表示
③可以指定成员的默认值,使用default定义
④自定义的注解没有成员,表示标识作用
(2)说明
①如果注解有成员,使用注解时,需要指明成员的值。
②自定义注解必须配上注解的信息处理流程(使用反射)才有意义
③自定义注解通常会指明两个元注解:Retention 、Target
4.元注解
(1)元注解:对现有的注解进行解释说明的注解
(2)举例:
- Retention : 用于指定所修饰的 Annotation 的生命周期
- RetentionPolicy.SOURCE:在源文件中有效(即源文件保留)
- RetentionPolicy.CLASS:在class文件中有效(即class保留)(默认行为)
- RetentionPolicy.RUNTIME:在运行时有效(即运行时保留)(只有声明为RUNTIME生命周期的注解,才能通过反射获取)
- Target : 用于指定被修饰的 Annotation 能用于修饰哪些程序元素
- Documented :表示所修饰的注解在被javadoc解析式,保留下来。默认情况下,javadoc是不包括注解的。
- Inherited: 被它修饰的 Annotation 将具有继承性。
5.如何获取注解信息:通过反射进行获取、调用
前提:要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME
6.jdk8中注解的新特性:
(1)可重复注解:
(2)类型注解:
- ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语 句中(如:泛型声明)。
- ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
四、集合
1、数组和集合
1.集合、数组是对多个数据进行存储操作的结构,简称java容器。
说明:此时的存储主要是指内存层面的存储,不涉及持久化的存储
2.数组存储的特点:
- 一旦初始化后,其长度就确定了
- 一旦定义,其元素的类型就确定了
3.数组存储的弊端:
- 一旦初始化后,其长度不可修改
- 数组中提供的方法有限,对于添加、删除、插入数据操作,非常不便,效率不高。
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。
- 数组存储数据是有序的,可重复的;不能满足无序、不能重复的需求。
3.集合存储的优点:
解决数组存储方面的弊端
2、Collection接口方法
1.集合框架
- Collection接口:单列数据,用来存储一个一个的对象
- List接口:有序、可重复的数据。—>“动态数组”
- ArrayList、LinkedList、Vector
- Set接口:无序、不可重复的数据。
- HashSet、LinkedHashSet、TreeSet
- List接口:有序、可重复的数据。—>“动态数组”
2.Collection接口常用方法
-
add(Object e):将元素e添加到集合coll中
-
size():获取添加的元素的个数
-
addAll(Collection coll1):将coll1集合中的元素添加到当前集合中
-
clear():清空集合元素
-
isEmpty():判断集合是否为空
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
-
contains(Object obj):判断当前集合中是否包含obj,在判断时会调用obj所在类的equals()
-
containAll(Collection coll1):判断形参coll1中的所有元素是否都存在与当前集合中
-
remove(Object obj):从当前集合中删除obj元素
-
removeAll(Collection coll1):从当前集合中删除coll1种所有的元素
-
retainAll(Collection coll1):获取当前集合和coll1集合的交集,并返回给当前集合
-
equals(Object obj):当前集合和形参集合的元素相同时返回true
-
hashCode():返回当前对象的哈希值
-
toArray():将集合转为数组
Arrays.atList():将数组转为集合
- iterator():返回Interator接口的实例,用于遍历集合元素
3.Collection集合与数组间的转换
toArray():将集合转为数组
Arrays.atList():将数组转为集合
//toArray():将集合转为数组
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length;i++){
System.out.println(arr[i]);
}
//Arrays.atList():将数组转为集合
List<String> list = Arrays.asList(new String[]{"aa", "bb", "dd"});
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123,456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123,456});
System.out.println(arr2.size());//2
4.向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
3、Iterator接口与foreach循环
1.遍历Collection的两个方式:
①使用迭代器Iterator②foreach循环(增强for循环)
2.java.utils包下定义的迭代器接口:Iterator
(1)说明
- Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
- GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
(2)作用:遍历集合Collection元素
(3)如何获取实例:coll.iterator()返回一个迭代器实例
Iterator iterator = list.iteratoe();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
(4)remove()
①在未调用next()或在上一次调用next()方法之后调用remove(),再调用remove()会出现IllegalStateException
②内部定义了remove(),在遍历时,可以删除集合中的元素
3.增强for循环(foreach循环)
1.遍历集合:
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("xiaoli"));
coll.add(false);
//for(集合元素的类型 局部变量 : 集合对象)
for (Object obj:coll) {
System.out.println(obj);
}
}
说明:内部仍然调用了迭代器
2.遍历数组:
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5};
//for(集合元素的类型 局部变量 : 集合对象)
for (int i :arr) {
System.out.println(i);
}
}
4、Collection子接口一:List接口
1.存储的数据特点:有序的、可重复的
2.常用方法
- void add(int index, Object ele):在index位置插入ele元素
- boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
- Object get(int index):获取指定index位置的元素
- int indexOf(Object obj):返回obj在集合中首次出现的位置,如果不存在返回-1.
- int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置,如果不存在返回-1.
- Object remove(int index):移除指定index位置的元素,并返回此元素
- Object set(int index, Object ele):设置指定index位置的元素为ele
- List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合,左闭右开
总结:
增:add(Object obj)
删:remove(int index)/remove(Object obj)
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);
}
private static void updateList(List list) {
list.remove(2);//1,2
list.remove(new Integer(2));//1,3
}
改:set(int index,Object ele)
查:get(int index)
插:add(int index,Object obj)
长度:size()
遍历:①Interator迭代器②增强for循环③普通循环
//方式一:Interator迭代器
Iterator iterator = list.iteratoe();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
//方式二:增强for循环
for(Object obj:list){
System.out.println(obj);
}
//方式三:普通循环
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
3.具体实现类
- Collection接口:单列数据,用来存储一个一个的对象
- List接口:存储有序、可重复的数据。—>“动态数组”
- ArrayList:作为List接口的主要实现类;线程不安全,执行效率高;底层使用Object[] elementData存储
- LinkedList:对于频繁的插入删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- Vector:作为List接口的古老实现类;线程安全的,执行效率低;底层使用Object[] elementData存储
- List接口:存储有序、可重复的数据。—>“动态数组”
4.源码分析
(1)ArrayList内存解析
①jdk7
ArrayList list = new ArrayList;//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
……
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
//开发中建议使用带参的构造器:ArrayList list = new ArrayList(int capacity);
②jdk8
ArrayList list = new ArrayList;//底层Object[] elementData初始化为{},并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData数组
//后续添加与扩容与jdk7一样
③总结
jdk7中ArrayList的对象的创建类似于单例的饿汉式;而jdk8中ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
(2)LinkedList源码分析:
(3LinkedList list = new LinkedList;//内部声明了node类型的first和last属性,默认值为null
list.add(123);//将123封装到node中,创建了node对象
//其中node定义为:体现了LinkedList的双向链表
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
(3)Vector源码分析:
jdk7和jdk8中通过Vector()构造器创建对象时,底层创建了长度为10的数组,扩容时,默认扩容为原来数组长度的2倍。
5.存储的元素的要求
添加的对象所在的类要重写equals()方法
6.ArrayList、LinkedList、Vector异同
相同:都实现类List接口,存储数据的特点相同:存储有序的、可重复的数据
不同:
- ArrayList作为List接口的主要实现类;线程不安全,执行效率高;底层使用Object[] elementData存储
- LinkedList对于频繁的插入删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- Vector作为List接口的古老实现类;线程安全的,执行效率低;底层使用Object[] elementData存储
5、Collection子接口二:Set接口
1.存储的数据特点:无序、不可重复的元素
具体的:
以HashSet为例说明:
(1)无序性:不等于随机性。存储的数据在底层数组中并非按照索引的顺序添加,而是根据数组的哈希值决定的
(2)不可重复性:用equlas()方法判断添加的元素时,不能返回true;即:相同元素只能添加一个
2.添加元素的过程:以HashSet为例说明:
向HashSet中添加元素a:调用元素a所在类的hashCode()方法,计算元素a的哈希值,哈希值通过算法计算出HashSet底层数据中的存放位置(即索引位置),判断此位置是否有元素:
- 没有其他元素,则元素a添加成功---->添加成功情况1
- 有其它元素b(或以链表形式存在多个元素),则比较元素a与元素b的hash值:
- hash值不相同,则元素a添加成功---->添加成功情况2
- hash值相同,需要调用元素a所在类的equlas()方法:
- equlas()返回true,元素a添加失败
- equlas()返回false,则元素a添加成功---->添加成功情况3
对于添加成功情况2和添加成功情况3,元素a与已经存在指定索引位置上的数据以链表的方式存储。
jdk7:元素a放到数组中,指向原来的元素
jdk8:原来的元素在数组中,指向元素a
HashSet底层:数组+l链表的结构(前提:jdk7)
3.常用方法
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法
4.具体实现类
- Collection接口:单列数据,用来存储一个一个的对象
- Set接口:存储无序、不可重复的数据。
- HashSet:作为set接口的主要实现类;线程不安全;可以存储null值
- LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历;在添加数据的同时,每个数据维护了两个引用,记录此数据的前一个数据和后一个数据;对于频繁遍历的操作,效率更高。
- TreeSet:可以按照添加对象的指定属性,进行排序
- HashSet:作为set接口的主要实现类;线程不安全;可以存储null值
- Set接口:存储无序、不可重复的数据。
5.存储对象所在类的要求
(1)Hashset/LinkedHashSet:
- 向set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashcode()和equals()
- 重写的hashcode()和equals()尽可能保持一致性:i昂等的对象必须具有相等的散列码
- 技巧:对象中用作equals()方法比较的Field,都用来计算hashcode值
(2)TreeSet:
- 自然排序:比较两个对象是否相同的标准为:comparaTo()返回0。
- 定制排序:比较两个对象是否相同的标准为:compara()返回0。
6.TreeSet
(1)说明
①可以按照添加对象的指定属性,进行排序
②向TreeSet中添加数据,要求是相同类的对象
(2)常用的排序方式
@Test
public void test3(){
Set set = new TreeSet();
set.add(new User("Tom",12));
set.add(new User("Jerry",22));
set.add(new User("Jim",20));
set.add(new User("Mike",42));
set.add(new User("Jack",42));
set.add(new User("Jack",33));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void test4(){
Comparator com = new Comparator() {
//年龄从小到大
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(), u2.getAge());
}else {
throw new RuntimeException("类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",22));
set.add(new User("Jim",20));
set.add(new User("Mike",42));
set.add(new User("Jack",42));
set.add(new User("Jack",33));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
6、Map接口
1.Map框架
-
Map:双列数据,存储key-value对的数据
-
HashMap:作为Map的主要实现类;线程不安全,效率高;存储null的key和value;
- LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个,对于频繁的遍历操作,此类执行效率高于HashMap。
-
TreeMap:保证按照添加的key-value对进行排序,实现排序遍历(自然排序)。底层是红黑树。
-
Hashtable:作为Map的古老实现类;线程安全,效率低;不能存储null的key和value;
- Properties:用来处理配置文件。key和value都是String类型。
-
HashMap的底层:jdk7:数组+链表;jdk8:数组+链表+红黑树
2.Map结构的理解
- Map中的Key是无序的、不可重复的,使用Set存储所有的key---->key所在的类要重写equals和hashCode(以HashMap为例)---->value所在的类要重写equals
- Map中的value是无序的、可重复的,使用Collection存储所有的value
- 一个键值对:key-value构成了一个Entry对象
- Map中的entry:无序的、不可重复的,使用Set存储所有的entry
3.常用方法
-
添加、删除、修改操作:
-
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
-
void putAll(Map m):将m中的所有key-value对存放到当前map中
-
Object remove(Object key):移除指定key的key-value对,并返回value
-
void clear():清空当前map中的所有数据
-
-
元素查询的操作:
-
Object get(Object key):获取指定key对应的value
-
boolean containsKey(Object key):是否包含指定的key
-
boolean containsValue(Object value):是否包含指定的value
-
int size():返回map中key-value对的个数
-
boolean isEmpty():判断当前map是否为空
-
boolean equals(Object obj):判断当前map和参数对象obj是否相等
-
-
元视图操作的方法:(遍历)
-
Set keySet():返回所有key构成的Set集合
-
Collection values():返回所有value构成的Collection集合
-
Set entrySet():返回所有key-value对构成的Set集合
-
@Test
public void test2(){
Map map = new HashMap();
map.put("AA", 123);
map.put(45, 234);
map.put("BB", 56);
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Collection values = map.values();
for (Object obj:values) {
System.out.println(obj);
}
Set set1 = map.entrySet();
Iterator iterator1 = set1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
}
总结常用方法:
添加: put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历:keySet()、values()、entrySet()
4.内存结构
(1)HashMap在jdk7中实现原理
HashMap map = new HashMap();
在实例化后,底层创建了长度是16的一维数组Entry[] table
……
map.put(key1,value1);
首先调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种计算以后,得到Entry数组中的存放位置。
- 如果此位置上数据为空,此时的key1-value1添加成功。---->添加成功情况1
- 如果此位置上数据不为空,(存在一个或多个数组(以链表形式存在)),比较key1和已经存在数据的哈希值
- 如果key1的哈希值与已经存在数据的哈希值不相同,此时key1-value1添加成功。---->添加成功情况2
- 如果key1的哈希值与已经存在数据(key2-value2)的哈希值相同,调用key1所在类的equals(key2)方法
- 如果equals返回false:此时的key1-value1添加成功。---->添加成功情况3
- 如果equals返回true:使用value1替换value2。
对于添加成功情况2和添加成功情况3,此时key1-value1和原来的数据以链表的方式存储。
扩容:默认的扩容为原来容量的2倍,并将原有的数据复制过来。
(2)HashMap在jdk8与jdk7中实现原理的不同
①HashMap map = new HashMap();在实例化后,底层没有创建长度是16的数组
②底层数据是Node[]而非Entry[]
③首次调用put方法时,底层创建长度为16的数组
④jdk7底层结构:数组+链表;jdk8底层结构:数组+链表+红黑树
形成链表时:jdk7新的元素指向旧的元素;jdk8旧的元素指向新的元素。
当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此索引位置上的所有数据使用红黑树存储。
(3)HashMap底层典型属性的属性说明
- DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
- DEFAULT_LOAD_FACTOR: HashMap的默认加载因子:0.75
- threshold:扩容的临界值,=容量×填充因子:16× 0.75 =>12
- TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值,转化为红黑树:8
- MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
(4)LinkedHashMap底层实现原理
LinkedHashMap底层实现结构与HashMap相同,LinkedHashMap继承于HashMap,区别在于LinkedHashMap内部提供了Entry,替换HashMap中的Node。
5.TreeMap的使用
向TreeMap中添加key-value,要求key必须是有同一个类创建的对象;按照key进行排序(自然排序、定制排序)
6.Properties:用来处理配置文件。key和value都是String类型
7、Collections工具类
1.Collections 是一个操作Collection(List、Set) 和 Map 的工具类
2.常用方法
-
排序操作:(均为static方法)
-
reverse(List):反转 List 中元素的顺序
-
shuffle(List):对 List 集合元素进行随机排序
-
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
-
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
-
swap(List,i,j):将指定 list 集合中的 i 处元素和 j 处元素进行交换
-
-
操作数组的工具类:Arrays 查找、替换
-
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
-
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
-
Object min(Collection) :根据元素的自然顺序,返回给定集合中的最小元素
-
Object min(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最小元素
-
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
-
void copy(List dest,List src):将src中的内容复制到dest中
-
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
-
说明:ArrayList和HashMap时线程不安全的,若要求线程安全,可以使用 synchronizedList(List list) 和 synchronizedMap(Map map)将ArrayList、HashMap转换为线程安全的。
3.Collection和Collections区别
- Collection,是单列集合的接口,有子接口List和Set
- Collections,是针对集合操作的工具类,其中包含对集合进行排序和二分查找的方法
五、泛型
1、泛型:把元素的类型设计成一个参数,这个类型参数叫做泛型。
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。
2、泛型在集合中的使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1EHkgWB1-1669557514434)(./img/9.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4KgIEpw7-1669557514435)(./img/10.png)](1) 集合接口或集合类在jdk5时修改为带泛型的结构
(2)在实例化集合类时,具体的指明泛型类型
(3)指明泛型类型后,在集合类或接口中定义类或接口时,定义的内部结构使用定义的类的泛型,都指定为实例化的泛型类型。
(4)注意:泛型的类型不能是基本数据类型;需要使用基本数据类型的位置定义为包装类
(5)实例化时没有指明泛型的类型,默认类型为java.lang.Object类型
3、自定义泛型结构:泛型类、泛型接口、泛型方法
类的内部方法可以使用类的泛型
(1)泛型类、泛型接口
-
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。
-
泛型类的构造器如下:public GenericClass(){}。 而下面是错误的:public GenericClass(){}
-
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
-
泛型不同的引用不能相互赋值。
-
如果定义了泛型类,实例化时要指明类的泛型:如果定义了泛型类,实例化没有指明类的泛型,则认为泛型类型为Object类型。
-
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
-
jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
-
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
-
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但静态方法中不能使用类的泛型。
-
异常类不能声明为泛型的
-
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
-
子类不保留父类的泛型:按需实现
-
没有类型 擦除
-
具体类型
-
-
子类保留父类的泛型:泛型子类
-
全部保留
-
部分保留
-
-
(2)泛型方法
- 在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系,泛型方法所属的类是不是泛型类都无所谓。
- 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定的。
4、泛型在继承上的体现
类A是类B的父类,G 和G是没有关系的,二者是并列关系
补充:类A是类B的父类,A
5、通配符
(1)通配符的使用
通配符:?
类A是类B的父类,G 和G是没有关系的,二者共同的父类是G<?>
(2)涉及通配符的集合的数据的写入和读取
- 添加(写入):对于List<?>不能向其内部添加数据;null除外
- 获取(读取):允许读取数据,读取的数据类型为Object
(3)有限制条件的通配符的使用
? extends A:
G<? extends A>可以作为G 和G的父类,其中B是A的子类
? super A:
G<? super A>可以作为G 和G的父类,其中B是A的子类
六、IO流
1、File类的使用
1.1File的理解
1.File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
2.File生命在java.io包下
3.File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入与读取文件内容的操作。如果需要读取或写入文件内容,必须使用io流。
1.2File的实例化
1.常用构造器
- public File(String filePash) 以filePash为路径创建File对象,可以是绝对路径或者相对路径,如果 filePash是相对路径,则默认的当前路径在系统属性user.dir中存储。
- public File(String parent,String child) 以parent为父路径,child为子路径创建File对象。
- public File(File parent,String child) 根据一个父File对象和子文件路径创建File对象
2.路径的分类
-
相对路径:相较于某个路径下,指明的路径。
-
Eclipse中:相对路径为当前的project。
-
IDEA中:使用JUnit单元测试方法测试,相对路径为当前Module。
使用main()方法测试,相对路径为当前的project。
-
-
绝对路径:包含盘符在内的文件或文件目录的路径。
3.路径分隔符:路径中的每级目录之间用一个路径分隔符隔开。
路径分隔符和系统有关:
-
windows和DOS系统默认使用“\”来表示
-
UNIX和URL使用“/”来表示
1.3File类的常用方法
-
File类的获取功能
- public String getAbsolutePath():获取绝对路径
- public String getPath() :获取路径
- public String getName() :获取名称
- public String getParent():获取上层文件目录路径。若无,返回null
- public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
- public long lastModified() :获取最后一次的修改时间,毫秒值
-
File类的获取功能:适用于文件目录
-
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
-
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
-
-
File类的重命名功能
- public boolean renameTo(File dest):把文件重命名为指定的文件路径
-
File类的判断功能
-
public boolean isDirectory():判断是否是文件目录
-
public boolean isFile() :判断是否是文件
-
public boolean exists() :判断是否存在
-
public boolean canRead() :判断是否可读
-
public boolean canWrite() :判断是否可写
-
public boolean isHidden() :判断是否隐藏
-
-
File类的创建功能 :创建硬盘中对应的文件或文件目录
-
public boolean createNewFile() :创建文件。若文件存在,创建失败,返回false
-
public boolean mkdir() :创建文件目录。如果此文件目录存在,创建失败。 如果此文件目录的上层目录不存在,创建失败。
-
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建。
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目 路径下。
-
-
File类的删除功能
- public boolean delete():删除文件或者文件夹
删除注意事项: Java中的删除不走回收站。
要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
2、IO流概述
1.流的分类
(1)按操作数据单位不同:字节流(byte)、字符流(char)
(2)按数据流的流向不同:输入流、输出流
(3)按流的角色不同:节点流、处理流
字节流 | 字符流 | |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
2.流的体系结构
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream | BufferedInputStream |
OutputStream | FileOutputStream | BufferedOutputStream |
Reader | FileReader | BufferedReader |
Writer | Filewriter | Bufferedwriter |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3gdq0W4-1669696767108)(./img/11.png)]
3.输入、输出的标准过程
(1)输入过程
①创建File文件,指明读取数据的来源(此文件必须存在)
②创建相应的输入流,将File对象作为参数,传入流的构造器中
③具体的读入过程
创建相应的byte[] 或char[]
④关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理
(2)输出过程
①创建File文件,指明写出数据的来源(此文件可以不存在)
②创建相应的输出流,将File对象作为参数,传入流的构造器中
③具体的写出过程
write(char[]/byte[] buffer,0,len)
④关闭流资源
说明:程序中出现的异常需要使用try-catch-finally处理
3、节点流(文件流)
3.1FileReader、FileWriter的使用:
1.FileReader的使用
(1)读入的文件必须存在,否则报FileNotFoundException异常
(2)异常的处理:需要使用try-catch-finally处理
(3)read():返回读入的一个字符,达到文件末尾,返回-1
public void test2() {
FileReader fr = null;
try {
// File类的实例化
File file = new File("hi.txt");
// 流的实例化
fr = new FileReader(file);
// 读入的操作
char[] cbuf = new char[5];//返回每次读入数组的字符个数,达到末尾返回-1
int len;
while ((len = fr.read(cbuf)) != -1){
for (int i = 0;i < len;i++){
System.out.print(cbuf[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.FileWriter的使用
(1)输出操作,对应file文件可以不存在。
(2)
-
File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件
-
File对应的硬盘中的文件如果存在,
-
对应流使用FileWriter(file,false)/FileWriter(file)构造器:覆盖原有文件
-
对应流使用FileWriter(file,false)构造器:在原有文件内容上添加内容
-
public void test3() throws IOException {
FileWriter fw = null;
try {
// 提供file类的对象,指明写出文件的位置
File file = new File("hello1.txt");
// 提供FileWriter的对象,用于数据的写出
fw = new FileWriter(file);
// 写出的操作
fw.write("I have a dream!\n");
fw.write("you need to have a dream" );
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.文本文件的复制
@Test
/*复制文件*/
public void test4() {
FileReader fr = null;
FileWriter fw = null;
try {
File srcfile = new File("hello1.txt");
File desfile = new File("hello2.txt");
fr = new FileReader(srcfile);
fw = new FileWriter(desfile);
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1){
//每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.2FileInputStream、FileOutputStream的使用:
1.对于文本文件(.txt,.java,.a,.cpp),使用字符流处理;
不能使用字符流来处理图片等字节数据
2.对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt),使用字节流处理
public void test2(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcfile = new File("图片1.jpg");
File destfile = new File("图片2.jpg");
fis = new FileInputStream(srcfile);
fos = new FileOutputStream(destfile);
byte[] buffer = new byte[5];
int len;
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、缓冲流–处理流之一
1.缓冲流:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
2.作用:提高流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区,默认情况下是8kb
3.举例
(1)BufferedInputStream和BufferedOutputStream:处理非文本文件
// 实现文件复制的方法
public void copyFileWithBuffered(String srcPash,String destPash) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 造文件
File srcfile = new File(srcPash);
File destfile = new File(destPash);
// 造流
// 造节点流
FileInputStream fis = new FileInputStream(srcfile);
FileOutputStream fos = new FileOutputStream(destfile);
// 造处理流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
// 复制
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != 0) {
bos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭:先关闭外层流,再关闭内层流
try {
if (bos != null)
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
// 说明:关闭外层流的同时,自动关闭内层流
// fos.close();
// fis.close();
}
}
(2)BufferedReader和BufferedWriter:处理文本文件
/*使用bufferedreader和BufferedWriter实现文本文件的复制*/
@Test
public void test3(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
// File srcfile = new File("dbcp.txt");
// File destfile = new File("dbcp1.txt");
// br = new BufferedReader(srcfile);
// bw = new BufferedWriter(destfile);
br = new BufferedReader(new FileReader(new File("dbcp.txt")));
bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
// 方法一
// char[] cbuf = new char[1024];
// int len;
// while ((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// bw.flush();//刷新
// }
// 方法二
String data;
while ((data = br.readLine()) != null){
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null)
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (br != null)
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5、转换流–处理流之二
1.转换流:属于字符流
InputStreamReader:将一个字节的输入流转换为字符的输出流—解码
OutputStreamWriter:将一个字符的输出流转换为字节的输出流—编码
说明:编码决定了解码的方式
2.作用:提供了在字节流和字符流之间的转换
举例:
/*InputStreamReader和OutputStreamWriter的使用*/
@Test
public void test2() throws IOException {
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
char[] cbuf = new char[1024];
int len;
while ((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
isr.close();
osw.close();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4bBEkuYW-1669696767109)(./img/12.png)]
说明:文件编码的方式,决定了解析时使用的字符集
3.解码:字节、字节数组–>字符数组、字符串
编码:字符数组、字符串–>字节、字节数组
4.字符集
常见编码表
- ASCII:美国标准信息交换码。 用一个字节的7位可以表示。
- ISO8859-1:拉丁码表。欧洲码表 用一个字节的8位表示。
- GB2312:中国的中文编码表。最多两个字节编码所有字符
- GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
- Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的 字符码。所有的文字都用两个字节来表示。
- UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
6、其他流
6.1标准的输入、输出流–处理流之三
System.in:标准的输入流,默认从控制台输入
System.out:标准的输出流,默认从控制台输出
2.System类的setIn(InputStream in) / setOut(PrintStream ps)方法重新指定输入和输出的流
6.2打印流–处理流之四
1.打印流:PrintStream和PrintWriter
实现将基本数据类型的数据格式转化为字符串输出
2.说明
- 提供了一系列重载的print() 和println()方法,用于多种数据类型的输出
- System.out返回的是PrintStream的实例
6.3数据流–处理流之五
1.DataInputStream 和 DataOutputStream
2.作用:用于读取或写出基本数据类型的变量或字符串
3.举例:
@Test
//将内存中的字符串、基本数据类型的变量写出到文件中
public void test3() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("小李");
dos.flush();//刷新
dos.writeInt(22);
dos.flush();
dos.close();
}
@Test
//将文件中保存的基本数据类型变量和字符串读取到内存中,保存在变量中。
//注意:读取不同类型的数据的顺序与写入文件时保存的数据的顺序一致
public void test4() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
String s = dis.readUTF();
int i = dis.readInt();
System.out.println(s);
System.out.println(i);
dis.close();
}
7、对象流–处理流之六
1.对象流:ObjectInputStream和OjbectOutputSteam
2.作用:用于存储和读取基本数据类型数据或对象的处理流
ObjectInputStream:内存中的对象—>存储中的文件、通过网络传输出去:序列化过程
OjbectOutputSteam:存储中的文件、通过网络接收进来—>内存中的对象:反序列化过程
3.对象序列化机制
允许把内存中的Java对象转换成平台无关的二进制流,从 而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传 输到另一个网络节点。
当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
4.序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
需要满足以下要求,才可实现序列化:
(1)需要实现Serializable接口(标识接口)
(2)当前类提供一个全局常量:serialVersionUID(序列版本号)
(3)处理当前类需要实现serialVersionUID的同时,其内部所有属性都必须是可序列化的(默认情况下,基本数据类型可序列化)
补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修 饰的成员变量
反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
8、RandomAccessFile流
1.随机存取文件流:RandomAccessFile类
2.说明:
1)RandomAccessFile继承与java.lang.Object类,实现了DataInput和OutPut接口
2)RandomAccessFile既可以作为输入流也可以作为输出流
3)RandomAccessFile作为输入流时,
如果写出到的文件不存在,则在执行过程中自动创建
如果写出到的文件存在,则在执行过程中覆盖原有文件(默认从头开始覆盖)
4)可以通过相关操作实现RandomAccessFile”插入“数据的效果。seek(int pos)
9、Pash、Pashs、Files的使用
1.NIO的使用说明
Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。
NIO与原来的IO有同样的作用和目 的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。
NIO将以更加高效的方式进行文件的读写操作。
2.Pash的使用说明
jdk7.0引入了Pash、Pashs、Files三个类
Pash、Pashs、Files声明在java.nio.file包中
2.1Pash的说明
Pash替换原有的File类。
Pash可以看作是java.io.file类的升级版本,也可以表示文件或文件目录,与平台无关。
2.2如何实例化Pash:使用Pashs类提供的静态 get() 方法
-
static Path get(String first, String … more) : 用于将多个字符串串连成路径
-
static Path get(URI uri): 返回指定uri对应的Path路径
2.3常用方法
- String toString() : 返回调用 Path 对象的字符串表示形式
- boolean startsWith(String path) : 判断是否以 path 路径开始
- boolean endsWith(String path) : 判断是否以 path 路径结束
- boolean isAbsolute() : 判断是否是绝对路径
- Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
- Path getRoot() :返回调用 Path 对象的根路径
- Path getFileName() : 返回与调用 Path 对象关联的文件名
- int getNameCount() : 返回Path 根目录后面元素的数量
- Path getName(int idx) : 返回指定索引位置 idx 的路径名称
- Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
- Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
- File toFile(): 将Path转化为File类的对象
3.Files工具类
3.1作用:操作文件或文件目录的工具类
3.2Files常用方法:
- Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
- Path createDirectory(Path path, FileAttribute … attr) : 创建一个目录
- Path createFile(Path path, FileAttribute … arr) : 创建一个文件
- void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
- void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除
- Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
- long size(Path path) : 返回 path 指定文件的大小
Files常用方法:用于判断
- boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
- boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
- boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
- boolean isHidden(Path path) : 判断是否是隐藏文件
- boolean isReadable(Path path) : 判断文件是否可读
- boolean isWritable(Path path) : 判断文件是否可写
- boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
Files常用方法:用于操作内容
- SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连 接,how 指定打开方式。
- DirectoryStream newDirectoryStream(Path path) : 打开 path 指定的目录
- InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
- OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
七、网络编程
1、InetAddress类的使用
1.网络编程中有两个主要的问题:
如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
找到主机后如何可靠高效地进行数据传输
2.网络通信中的两个要素
(1)IP和端口号
(2)提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
3.通信要素一:IP和端口号
(1)IP的理解
①IP:唯一的标识Internet上的计算机(通信实体)
②在java中使用InetAddress类代表IP
③IP分类:IPv4和IPv6;万维网和局域网
④域名:比如:www.baidu.com
域名解析:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS) 负责将域名转化成IP地址,这样才能和主机建立连接。
⑤本地回路地址:127.0.0.1 对应着localhost
(2)InetAddress类:此类的一个对象就代表着一个具体的IP地址
①实例化InetAddress方法:getByName(String host) 、getLocalHost()
常用方法:getHostName() 、getHostAddress()
②端口号:表示计算机上正在运行的进程。
要求:不同的进程有不同的端口号
范围: 被规定为一个 16 位的整数 0~65535
③ 端口号与IP地址的组合得出一个网络套接字:Socket。
4.通信要素二:网络通信协议
(1)分型模型
(2)TCP和UDP的区别
-
TCP协议
-
使用TCP协议前,须先建立TCP连接,形成传输数据通道
-
传输前,采用“三次握手”方式,点对点通信,是可靠的
-
TCP协议进行通信的两个应用进程:客户端、服务端。
-
在连接中可进行大数据量的传输
-
传输完毕,需释放已建立的连接,效率低
-
-
UDP协议:
-
将数据、源、目的封装成数据包,不需要建立连接
-
每个数据报的大小限制在64K内
-
发送不管对方是否准备好,接收方收到也不确认,故是不可靠
-
可以广播发送
-
发送数据结束时无需释放资源,开销小,速度快
-
(3)TCP三次握手和四次挥手
2、TCP网络编程
(1)客户端发送信息给服务端,服务端将数据显示在控制台上
(2)客户端发送文件给服务端,服务端将文件保存在本地
(3)客户端发送文件给服务端,服务端将文件保存在本地,并返回”发送成功“给客户端,并关闭相应的连接。
3、UDP网络编程
TCP:可靠的数据传输(三次握手);进行大数据量的传输;效率低
UDP:不可靠;以数据报的形式发送,数据报限定为64k;效率高
public class UDPTest {
@Test
// 发送端
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "UDF方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close();
}
@Test
// 接收端
public void receiver() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
}
4、URL网络编程
(1)URL:统一资源定位符,对应着互联网的某一资源地址
(2)格式:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
http://192.168.14.100:8088/examples/hello.txt?username=Tom
(3)如何实例化:
URl url = new URl(“http://192.168.14.100:8088/examples/hello.txt?username=Tom”);
(4)常用方法
- public String getProtocol( ) 获取该URL的协议名
- public String getHost( ) 获取该URL的主机名
- public String getPort( ) 获取该URL的端口号
- public String getPath( ) 获取该URL的文件路径
- public String getFile( ) 获取该URL的文件名
- public String getQuery( ) 获取该URL的查询名
(5)可以读取、下载对应的url资源