Java高级应用
异常处理
异常处理概述
异常:指的是程序在执行过程中,出现的非正常情况【并不是指语法错误(将不会编译,不会执行)和逻辑错误(和自己预期的答案不一样)】,如果不处理最终会导致JVM的非正常停止
异常的抛出机制:Java把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常的对象,并且抛出(throw)。然后程序员可以**捕获(catch)**这个异常对象并处理
异常的根与基本的子类:Error、Exception extends Throwable(所有异常的根类)
- Error类:Java虚拟机无法解决的严重问题。比如:JVM系统内部错误、资源耗尽等严重问题【StackOverFlowError(栈内存溢出)和OutOfMemoryEorror(堆内存溢出)】一般不编写针对性的代码进行处理
- Exception类:其它因编程或者偶然的外在因素导致的一般性问题,需要用针对性的代码进行处理。
——编译时异常(受检异常):除了RuntimeException,不让你运行就报错了
——运行时异常(非受检异常):RuntimeException【ArrayIndexOutBoundsException数组越界、NullPointerException空针异常、NumberFormatException格式异常】
为什么要异常处理:编写程序时可能需要必然出现错误的情况,可能需要许多的if-else语句导致代码的冗余,故Java的异常处理机制是将异常处理的程序代码集中在一起,与正常代码分开,使得程序简洁。异常的处理方式有两种:
- try-catch-finally:直接解决异常
- throws+异常类型:直接解决不了所以抛给上一级调用者使用第一种方式解决,要是还解决不了就继续抛。
try-catch-finally(抓抛模型)
- 抛(try):程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,生成对应异常类的对象,并将此对象抛出,一旦抛出,此程序就不在执行其后的代码了。
- 抓(catch):对抛出的异常进行捕获处理,一定将异常进行了处理,代码就可以继续执行【是指finally后的代码能执行,不是产生异常代码行后面的代码】。
基本的结构与使用细节:
try{
…………
…………//这行可能产生异常的代码,要是产生异常后面的代码行不再执行,毕竟也没有执行的必要了;try中声明的变量,除了try结构后就不能进行调用了
…………
…………
}
catch(异常类型1 e){
…………//当产生异常类型1型异常的处置措施,这和下面的catch是并列供选择匹配的关系,但后面catch中的异常类型不能是此类型的子类,不然后面的就不会执行了【这里面可以是自己编写的代码,也可以是父类Throwable中的方法printStackTrace()打印异常的详细信息和getMessage()获取发生异常的原因;】【运行型异常一般不适用catch抓获,而编译型异常必定要使用catch抓获】
}
catch(异常类型2 e){
}
finally
{
…………//无论是否发生异常,都无条件执行的语句无论try与catch中是否有return语句.【资源关闭的语句必须要声明finally里面,保证程序不会浪费资源】
}
throws
throws的结构与注意细节:
public void test() throws 异常类型1,异常类型2,……{
//可能存在编译时异常,故把异常抛(throws)出去,抛出去后自己的异常不会报错但没有解决,但交给了test2()继续循环下去直至test3()能自己用try-catch-finally解决
}
public void test2() throws 异常类型1,异常类型2,……{//test()方法中的异常test2()可能处理不来,继续像上面一样继续抛出,直至test3用()try-catch-finally解决
test();
//可能存在编译时异常
}
public void test3() throws {//test()方法中的异常test3()能处理来用()try-catch-finally解决
try{
test2();
}
catch(异常类型 e){
}
finally{
}
}
注意:在重写的过程中,子类重写方法抛出的异常类型必须是父类被重写方法抛出的异常类型的子类,根据以上throws处理异常类型的过程可以知道表面上是使用try-catch-finally解决的是父类被重写方法抛出的异常类型,但实际是解决更深层次子类抛出的异常,要是子类异常包含父类异常,那么最终异常可能会处理不到位,故比如接口中某些抽象方法没有方法体也要抛出异常是为了给实现类重写抛出异常用。故选择处理异常的方式如下:
——资源一定要被执行,使用try-catch-finally
——父类没设有throws,则子类必须要低于父类则只能使用try-catch-finally
——有层次的类,使用throws
###throw
通过throw手动抛出异常对象:
手动抛出异常对象的原因:在实际开发中,如果出现不满足具体场景的代码问题,我么就需要手动抛出指定类型的异常对象。举例:
public class ThrowTest{
……main(……){
Student s1 = new Student();
try{
s1.regist(-10);
print(s1);
}catch(Exception e){
}
}
}
class Students{
int id;
public void regist throws Exception{
if(id>0){
this.id = id;
}else{
throw new Exception();//自己抛出异常
}
}
}
注意:throw后面的代码不能执行,编译不通过
异常类的定义
public class Axception(自定义异常类) extends Exception(现有异常类型){
内部有现有模板
}
多线程
程序、进程、线程与并行、并发
程序:为了完成特定任务,用某种语言编写的一组指令集合,即一段静态的代码。
进程:程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的,进程是动态的,进程作为操作系统调度和分配资源的最小单位
线程:进程进一步细化为线程,是程序内部的一条执行路径,线程作为CPU调度和执行的最小单位
线程和进程的内存问题:
说明:一个进程中的多个线程共享相同的内存单元(线程共享区,一个进程只有一个),它们从同一个堆中分配对象,可以访问相同的变量和对象,这就使得线程之间的通信更加简便、高效,但是也会导致并发问题;但每个线程都有一个自己的线程隔离区;不同的进程一般是不会共享内存的(有的会,比如支付宝调用各种程序)
并行和并发:
-
并行:指两个或多个事件在同一时刻发生。指在同一时刻,有多条指令在多个CPU上同时执行。比如:多个人同时做不同的事
-
并发:指两个或多个事件在同一时间段内发生。即在一段时间内,有多条指令在单个CPU上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果
线程的创建方式(继承Thread类、实现Runnable接口)
通过Thread类创建:(1)、创建一个继承于Thread类的子类(2)、重写Thread类的run()---->将此线程要执行的操作,声明在此方法中(3)、创建当前Thread的子类的对象(4)、通过对象调用start()【启动线程调;用线程的run方法】
class PrintNumber extends Thread{
public void run(){
System.out.println("分线程");
}
}
public class Test{
public static void main(String[ ] args){
PrintNumber t1 = new PrintNumber();
t1.start();//在主线程执行的道路上开辟了分线程执行的道路
………………………………………………………………………………………………………………………
//创建Thread类的匿名子类的匿名对象
new Thread(){
public void run(){
System.out.println("分线程");
}
}.start();
//另一种写法
Thread t1 = new Thread(){
public void run(){
System.out.println("分线程");
}
}.start();
……………………………………………………………………………………………………………………
System.out.println("主线程");
}
}
注意:不能让已经t1.start()的线程,再次执行t1.start(),否则会报错
实现Runnable接口创建[建议]:(1)、创建一个实现Runnable接口的类(2)、实现接口中的run()—>将此线程要执行的操作,声明在此方法体中(3)、创建当前实现类的对象(4)、将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例(5)、Thread类的实例调用start();
class Print implements Runnable{
private int money = 1000;//由于下面只要创建一个p对象,所以这个money很好能实现多线程的共享,但Thread创建不行,这是一个优点
public void run(){
System.out.println("分线程");
}
}
public class Test{
public static void main(String[ ] args){
Print p = new Print();
Thread t1 = new Thread(p);
t1.start();
//创建第二个线程
Thread t2 = new Thread(p);
t2.start();
………………………………………………………………………………………………………………………
new Thread(new Runnable(){//按照上面Thread类以及接口的匿名子类的匿名对象的写法应该是new Runnable(){}.start()但是在此之前啊还需要创建Thread类的对象,故要改成new Thread(new Runnable(){}).start();
public void run(){
System.out.println("分线程");
}
}).satrt();
………………………………………………………………………………………………………………………
System.out.println("主线程");
}
}
Thread常用的方法与生命周期
线程中的构造器:public Thread1 extendsThread{}
——public Thread():分配一个线程构造器,子类要是没有定义构造器,则创建子类实体用的就是父类的构造器
——public Thread(String name):分配一个指定名字的新的线程对象,子类需要重写父类的构造器:public void Threa1{super(name);}
——public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run();
——public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字
常用的方法:
- start():
- run():
- currentThread():获取当前执行代码对应的线程
- getName():获取线程的名称
- setName():改正线程的名称
- sleep():让线程睡一段时间,这个方法默认抛出异常,因此调用时需要try-catch
- yield():静态方法,释放CPU的执行权
- join():这个方法默认抛出异常,因此调用时需要try-catch,在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才会执行
- isALive():线程是否存活
- getPriority():获取线程的优先级
- setPriority():设置线程的优先级
线程的生命周期:
同步代码块解决两种线程创建方式的线程安全问题
线程的安全问题举例(买车票):
class SaleTicket implements Runnable{
int ticket = 100;//下面只使用一个SaleTicket实例,故可以直接声明在这
public void run(){
while(true){
if(ticket>0){
try{
Thread.sleep(10);
catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread.getName()+""+ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowTest{
public static void main(String[] args){
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
以上代码安全问题出现的原因(票号会出现重复或者负数):
同步代码块解决线程问题:必须保证一个线程a在操作ticket的过程中,其他线程必须等待(上锁),直到线程a操作ticket结束以后(解锁),其他线程才可以进来继续操作,上锁代码synchronized(同步监视器){需要被同步的代码}
【**需要被同步的代码即为操作共享数据的代码,比如多个线程共享操作的数据ticket;同步监视器就是锁,可以用任何一个类充当,但必须唯一,某个线程用了其他线程就不能用了,例如下面代码 **】;
class SaleTicket implements Runnable{
int ticket = 100;//下面只使用一个SaleTicket实例,故可以直接声明在这
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){//只允许某一个线程在同步代码块里面操作,其他的线程均需在外面等待,知道代码块里面的线程操作完成,因为下面SaleTicket只创建了一个实例,那么obj也只使用一次,也可以使用this、SaleTicket.class
if(ticket>0){
try{
Thread.sleep(10);
catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread.getName()+""+ticket);
ticket--;
}else{
break;
}
}}
}
}
public class WindowTest{
public static void main(String[] args){
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
同步方法解决两种线程创建方式的线程安全问题
说明:如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法
class SaleTicket implements Runnable{
int ticket = 100;//下面只使用一个SaleTicket实例,故可以直接声明在这
boolean isFlag = true;
public void run(){
while(isFlag){
show();
}
public synchronized void show(){//此时是非静态的方法,默认的同步监视器是this,当然下面的SaleTicket只创建了一个实例,故可以直接在方法使用synchronized,但是要是像继承Thread创建线程那样需要创建多个实例则不能使用这种形式,还得使用同步代码块,要是是静态方法,则同步监视器就是SaleTicket.class那么就都可以使用
if(ticket>0){
try{
Thread.sleep(10);
catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread.getName()+""+ticket);
ticket--;
}else{
isFlag = false;
}
}
}
}
public class WindowTest{
public static void main(String[] args){
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
同步方法注意:在上面代码的注释中
弊端:synchronized会降低执行效率
线程安全的懒汉式_死锁_ReentrantLock的使用
实现线程安全懒汉式:
public class BankTest{
static Bank b1 = null;
static Bank b2 = bull;
public static void main(String [ ] args){
Thread t1 = new Thread(){//是继承Thread类创造线程的方式,本应该是Bank t1 = new Bank();t1.start();但是匿名子类的的写法就是父类Thread写左边,new Thread()写右边变成Thread t1 = new Thread(){重写run方法}
public void run(){
b1 = Bank.getInstance();
}
};
Thread t2 = new Thread(){//
public void run(){
b2 = Bank.getInstance();
}
};
t1.start();
t2.start();
}
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
if(instance == null){
try{
Thread.sleep(10);//让先进来的线程挂起,不执行下面instance的赋值操作,所以instance还是为null,故另一个线程还是可以进入这个条件语句,因而可能会创建出两个Bank实例
}catch(InterException e){
}
instance = new Bank();
}
return instance;
}
}
注意:此时由于线程的并发性,会导致创建两个Bank的实例,违背了单例创建模式的规则,故需要把getInsatnce()要改成同步方法或者同步代码块:
class Bank{
private Bank(){}
private static Bank instance = null;
public static synchronized Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance;
}
}
………………………………………………………………………………………………………………………………
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
if(instance == bull){//这里属于优化代码,因为instance是static共享的,故某个线程使用完后,它将不再为空,其他线程将经过判断后不在执行下面的代码,节省了时间
synchronized(Bank.class){
if(instance == null){
i nstance = new Bank();
}
}
}
return instance;
}
}
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
public class DeadLockTest{
public static void main(String[ ] args){
new Thread(){
声明两个唯一的类s1、s2;
public void run(){
synchronized(s1){
//执行语句 //线程要执行下去要等待s2放开,但s2被下面用着
synchronized(s2){//执行语句}
}
}
}.satrt();
new Thread(){
声明两个唯一的类s1、s2;
public void run(){
synchronized(s2){
//执行语句 //线程要执行下去要等待s1放开,但s1被上面面用着
synchronized(s1){//执行语句}
}
}
}.satrt();
}
}
诱发死锁的原因以及解决方法:
- 原因:(1)互斥条件、(2)占用且等待、(3)不可抢夺、(4)循环等待【同时出现以上四个条件就会出发死锁】
- 解决死锁:(1)互斥条件基本上无法被破坏。因为线程需要通过互斥解决问题、(2)可以考虑一次性申请所有所需资源,这样就不存在等待问题、(3)占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源(4)可以将资源改为线性资源。申请资源时,先申请序号较小的,这样避免循环等待问题。【只要解决其中一个问题即可】
ReentrantLock:
使用方法:private static final ReentrantLock lock = new ReentrantLock();//因为锁的唯一性,所以要private static final 修饰,但实现Runnable不需要;lock.Lock();//同步代码块//lock.unlock();
class SaleTicket implements Runnable{
int ticket = 100;//下面只使用一个SaleTicket实例,故可以直接声明在这
private static final ReentrantLock lock = new ReentrantLock();//因为锁的唯一性,所以要private static final 修饰,但实现Runnable可不用;
public void run(){
while(true){
try{
lock.lock();
if(ticket>0){
try{
Thread.sleep(10);
catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread.getName()+""+ticket);
ticket--;
}else{
break;
}
}finally{
lock.unlock();//lock.unlock一定要执行,故要使用finally
}
}
}
}
public class WindowTest{
public static void main(String[] args){
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
线程间的通信机制(生产者与消费者)
线程的通信:当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制(wait and notify)可以协调它们的工作,以此实现多线程共同操作一份数据。
举例:交替打印1-100
class PrintNumber implements Runnable{
private int number = 100;//下面只使用一个PrintNumber实例,故可以直接声明在这
public void run(){
while(true){
synchronized(this){//有锁才能进入代码块
notify();
if(number<=100){
System.out.println(Thread.currentThread.getName()+":"+number);
number++;
try{
wait();//线程一旦执行此方法,就会进入等待状态,同时会释放同步监视器的调用
}catch(InterruptedExcepton e){
e.ptintStackTrace();
}
}else{
break;
}
}
}
}
}
public class PrintNumberTest{
public static void main(String[] args){
PrintNumber p = new PrintNumber();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
涉及到三个方法的使用:
- wait():线程一旦执行此方法,就会进入等待状态。同时会释放对同步监视器的调用【与sleep的区别 】
- notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的哪一个线程。(如果被wait()的多个线程的优先级相同,则随机唤醒一个),被唤醒的线程从当初被wait的位置继续执行
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
- 注意:此三个方法的使用,必须是在同步代码块或同步方法中,并且此三个方法的调用者必须是同步监视器
生产者与消费者:
class Clerk{//店员
private int productNum = 0;
//增加产品数量的方法
public void addProduct(){
if(productNum>=20){
try{wait()}catch(…… e){e.printStackTrace();}
productNum++;
ptint(生产了productNum个产品);
notify;
}
public void minusProduct(){
if(productNum<=0){
try{wait()}catch(…… e){e.printStackTrace();}
ptint(消费了productNum个产品);
productNum--;
notify();
}
//减少产品数量的方法
}
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk){//构造器
this.clerk = clerk;
}
public void run(){
while(true){
print("生产者开始生产产品");
clerk.addProduct();//省略了this.clerk.addProduct()
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Producer(Clerk clerk){//相当于get方法
this.clerk = clerk;
}
public void run(){
while(true){
print("生产者开始消费产品");
clerk.minusProduct();
}
}
}
public class ProducerConsumerTest{//测试
public static void main(String[ ] args){
Clerk clerk = new Clerk();
Producer pro1 = new Producer(clerk);
Consumer con1 = new Consumer(clerk);
}
}
线程的创建方式(实现Callable与线程池)
Callable:
class NumThread implements Callable{
public Object call() throws Exception{//继承Callable能够返回值并且还能够抛出异常
int sum = 0;
for(int i = 1;i<=100;i++){
if(i%2 ==0){
System.out.println(i);
sum +=i;
}
}
return sum;
}
public class CallableTest{
public static void main(String[ ] args){
NumThread numthread = new NumThread();
Thread t1 = new Thread();
t1.start();
try{
Object sum = futureTask.get();//得到返回值的用法
print(sum);
}catch(…… e){
e.printStackExcption;
}
}
}
线程池:
具体见线程池
常用类与基础API
String
一些注意点:【String是不可变序列】,一旦字符串常量声明在字符串池中就不能改变,要是想改变就必须重新再在字符串池中重新声明一个字面量,【String s1=“hello”,s1=“hell”,此时字符串池中有两个字面量hello和hell】字符串常量都存储在字符串常量池(StringTable)中;字符串常量池不允许存放两个相同的字符串常量;
- String s2 = new String(“hello”);在内存创建了几个对象?两个!一个是堆空间中new对象。另一个是在字符串常量池中生成的字面量【字符串常量池中的字面量是没有相同的,不同的字面量的地址是不同的】
- String的连接操作:+
——常量+常量:结果仍然存储在字符串常量池中,返回此字面量的地址。【此时的常量可能是字面量,也可能是final修饰的常量】
——常量+变量 或 变量+常量:都会通过new的方式创建一个新的字符串,返回堆空间中此字符串对象的地址
——调用字符串intern():返回的是字符串常量池中字面量的地址
——s1.concat(s2):不管是常量还是变量调用此方法,同样不管参数是常量还是变量,总之,调用完concat()都会返回一个新new的对象
构造器:public String()
:构造空字符串对象;public String(String original)
:构造与形参相同的字符序列;public String(char[ ] value)
:通过当前参数中的字符数组来构造新的String;public String(char[ ] value,int offset,int count)
:通过当前参数中的字符数组的一部分来构造新的String;public String(byte[ ] bytes)
:通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String;public String(byte[ ] bytes,chasename)
:通过使用指定的字符解码当前参数中的字符数组来构造新的String
String与char[ ]、String与byte[ ]的相互转化:char[ ] arr = str.toCharArray();
构造器转回来;byte[ ] arr = str.getBytes(字符集码类型);
构造器转回来
方法:
boolean isEmpty()
:字符串是否为空int length()
;返回字符串的长度String concat()
:拼接boolean equals(Object obj)
:比较字符串是否相等,区分大小写boolean equalsIgnoreCase(Object obj)
:比较字符串是否相等,不区分大小写int compareTo(String other)
:比较字符串大小,区分大小写,按照Unicode编码值比较大小int compareToIgnoreCase(String other)
:比较字符串大小,不区分大小写,按照Unicode编码值比较大小String toLowerCase()
:将字符串中大写字母转为小写String toUpperCase()
:将字符串中小写字母转为大写String trim()
:去掉字符串前后空白符public String intern()
:结果在常量池中共享的地址
StringBuffer与StringBuilder
注意:【StringBuffer与StringBuilder是可变序列】字符串常量可以在原有的基础上改变,不需要重新像String那样在创建一个字面量,因此针对开发中需要频繁增删改操作的字符串、 不涉及到线程安全问题 或 如果开发中大体确定要操作的字符的个数 【使用带参的构造器StringBuffer(int capacity)确定底层字符串数组的大小,避免频繁扩容影响效率】,建议使用StringBuffer和StringBuilder;
方法:
- 增:
append(xx);//链式操作
- 删:
delete(int start,int end)【左闭右开】;delete(int index)
- 改:
replace(int start,int end)【左闭右开】;setchar(int index,char c)
- 查:
charAt(int index)
- 插:
insert(int index,xx)
- 长度:
length()
- 反转:
reverse()
日期时间API、 Comparable接口实现自然排序、Comparator实现定制排序及对比、BigInteger、BigDecimal和Random
说明:常用API的使用了解即可,具体使用可看尚硅谷康老师的讲解
集合框架
集合架构体系
概述:
- 数组的弊端:
——数组一旦初始化,其长度就不可变了。
——数组中存储数据特点的单一性。对于无序的、不可重复的场景的多个数据就无能为力了。
——数组中可用的方法属性都很少,具体的需求,都需要自己来组织相关的代码逻辑
——针对于数组中元素的删除、插入操作,性能较差。
Java集合框架体系(java.util包下)
- java.util.Collection:【Collection是一个接口】存储一个一个的数据
|--------子接口:List:存储有序、可重复的数据【动态数组】
|-----------------实现类:ArraayList(主要实现类)、LinkedList、Vector
|--------子接口:set:存储无序、不可重复的数据【高中学习的集合】
|-----------------实现类:HashSet(主要实现类)、LinkedHashSet、TreeSet
- java.util.Map:【Map是一个接口】存储一对一对的数据(key-value键值对,类似于哈希函数对应关系)
|--------实现类:HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties
注意:集合只能操作引用数据类型,不能像数组那样既能操作基本数据类型又能操作引用数据类型
Collection【接口】
使用:由于Collection是个接口,要想创建实例对象演示接口中的抽象方法,必须利用到多态性:Collection coll = new ArrayList();
【ArratList是Collection的实现类】
Collection的一些方法:
add(Object obj)
:添加元素到当前集合中,都是引用数据类型【也可以把集合当作一个元素添加进去】,要是基本数据类型,它会自动装箱addAll(Collection other)
:添加other集合中的所有元素对象到当前集合中。size()
:集合中元素的个数,如果集合中还有另一个集合,那么另一个集合是当作一个元素boolean isEmpty()
:判断当前集合是否为空集合boolean contains(Object obj)
:判断当前集合是否存在一个与obj对象equals返回true的元素【自定义的obj必须要重写equals()方法用于判断具体内容,否则就是contains就是比较对象的地址来判断是否包含】boolean containsAll(Collection coll)
:判断集合coll是否属于调用集合boolean equals(Object obj)
:判断当前集合是否与obj相等void clear()
:清空集合元素boolean remove(Object obj)
:从当前集合中删除第一个找到的与obj对象equals返回true的元素。boolean removeAll(Collection coll)
:从当前集合中删除所有与coll集合中相同的元素。boolean retainAll(Collection coll)
:从当前集合中删除两个集合中不同的元素 ,即是返回两个集合的交集Object[ ] arr = coll.toArray()
:返回包含当前集合中所有元素的数组,即把集合转换成对象数据类型的数组【数组转换成集合Arrays.asList(arr)】hashCode()
:获得集合对象的哈希值iteratar()
:返回迭代器对象,用于集合遍历
迭代器(Iteratar):用来遍历集合对象的,是个接口,同样需要使用多态来创建它的实例对象一遍来使用它的抽象方法,模板获得迭代器的对象:Iterator iterator = coll.iterator();
【集合的iterator()方法返回的是Iterator接口的实现类的对象,以后就这样写即可,不要自已具体的实现类是什么】
如何实现遍历:
Iterator iterator = coll.iterator();//创捷迭代器对象
while(iterator.hasNext()){//判断当前指针下一个位置还有元素没
System.out,println(iterator.next())//(1)指针下一(2)将下移以后集合位置上的元素返回
}
注意:interator.next()返回的时Object类的对象
增强for循环(foreach),用于遍历循环
格式:for(要遍历的集合或数组元素的类型 临时变量 :要遍历的集合或数组变量){操作临时变量的输出}【底层仍然使用是迭代器】
for(Object obj:coll){print(obj);}//打印集合coll中的所有的Object类的对象元素
List【Collection的子接口】
方法:(1)、Collection中声明的15个方法 (2)、因为List是有序的,进而就有索引,进而就会增加一些针对索引操作的方法。
- 增:
add(Object obj)、addAll(Collection coll)
- 删:
remove(Object obj)、remove(int index)
- 改:
set(int index,Object ele)
- 查:
get(int index)
- 插:
add(int index,Object ele)、addAll(int index, Collection eles)
- 长度:
size()
- 遍历:
iteractor()
:使用迭代器进行遍历;增强for循环
Set【Collection的子接口】
方法:因为Set存储无序的、不可重复的数据,因此与Collection父接口相似,故有Collection中声明的15个方法
实例对象创建:Set set = new HashSet();//主要的实现类
- HashSet:【Set的主要实现类】;底层使用的是HashMap,创建的集合对象不可以按照添加元素的顺序实现遍历。
- LinkedHashSet:【HashSet的子类】创建的集合对象可以按照添加元素的顺序实现遍历。
- TreeSet:创建的集合对象可以按照添加元素的顺序实现遍历。
HashSet、LinkedHashSet的特性:
- 无序性:无序性不等于随机性(随机性指的是不是根据元素添加的顺序遍历);无序性是指元素存放的位置不像ArrayList按照添加顺序那样依次紧密排列的,而是根据元素关键字的哈希值排在内存的不同位置
- 不可重复性:添加到Set中的元素是不能够相同的,比较的标准需要判断hashCode()得到的哈希值与equals()得到的结果,【因此想要添加到Set集合中的自定义类必须要重写equals()与hashCode()方法】
- 注意:要求元素所在的类要重写两个方法:
equals()
和hashCode()
TreeSet的特性及要求:
- 要求添加到TreeSet中的元素必须是同一类型的【因为TreeSet中的元素之间需要比较才能按照顺序实现遍历,故需要同一类型的元素,但是集合存放元素不限制类型,我们只能通过泛型实现限制】,否则会报错
- 判断数据是否相同的标准不再是考虑hashCode()和equals()方法了,意味着添加到TreeSet中的元素所在的类不需要重写hashCode()和equals();比较元素大小或比较元素是否相等的标准就是考虑自然排序或定制排序中,
compareTo()
或者compare()
的返回值【返回值为0则表示两个对象相等】 - 向TreeSet中添加元素需要考虑排序:自然排序【
TreeSet = new TreeSet()
添加自定义类元素默认为自然排序,与自定义类中compareTo重写方法有关】和定制排序【Comparator comparator =new Comparator{public int compare(obj1,obj2){自己定义排序规则}} TreeSet = new TreeSet(comparator)
使用定制排序】 具体见TreeSet中排序的实现
Map【接口】
Map及其实现类的对比:
java.util.Map:存储一对一对的数据(key-value键值对entry,(x1,y1)、(x2、y2)---->y=f(x),类似与高中的函数)
|-------HashMap:主要实现类;线程不安全的,效率高;可以添加null的key和value值;底层使用数组+单向链表+红黑树结构存储
|-------Hashtable:古老实习类;线程安全的,效率低;不可以添加null的key或value值;底层使用数组+单向链表结构存储
|-------------------LinkedHashMap:是HashMap的子类;在HashMap使用的数据结构基础上,增加了一对双向链表,用于记录添加的元素的先后顺序,进而我们在遍历元素时,就可以按照添加的顺序显示。开发中,对于频繁的遍历操作,建议使用此类
|-------TreeMap:底层使用红黑树存储;可以按照添加的key-value中的key元素的指定的属性的大小顺序尽心遍历。需要考虑使用(1)、自然排序(2)、定制排序
|-------------------Properties:其key和value都是String类型。常用来处理属性文件。
Map中的常用方法:
- 增:
put(Obejct key,Object value);putAll(Map m)
- 删:
Object remove(Object key)
- 改:
put(Obejct key,Object value);putAll(Map m)
- 查:
Object get(Object key)
- 长度:
size()
- 遍历:遍历key集:
Set keySet();
遍历value集:Collection values;
遍历entry集:Set entrySet()
遍历:
Map map = new HashMap()//创建Mao集合对象
………………………………………………key的遍历……………………………………………
Set keySet = map.keySet();//通过keySet方法得到包含key元素的Set集合
Iterator iterator= keySet.iterator();//Set集合的遍历方式
while(iterator.hasNext()){
Object key = iterator.next();//因为iterator.next()返回的是Object类且key也不知具体的类型,就把key当作Object的类型进行打印
System.out.println(key);
}
…………………………………………value的遍历……………………………………………
Collection values = map.values();
//使用for
for(Object obj:values){
System.out.println(obj);
}
…………………………………………entry的遍历……………………………………………
//第一种方式
Set entrySet = map.entrySet();
Iterator iterator= entrySet.iterator();
while(iterator.hasNext()){
Map.Entry entry = (Map.Entry)iterator.next();//Entry是Map接口中的一个静态内部接口(参照类的静态内部类,接口也有内部接口),里面有getKey和getValue方法我们需要使用【而Node类是Map.Entry接口的实现类】,所有我们要将iterator.next()返回的Object类的对象强转为Node类(Map.Entry接口的实现类),再将Node类多态于Map.Entry接口,以便使用Node类中的getKey和getValue方法,所以就这样写
System.out.println(entry.getKey+entry.getValue);//key和value单独输出
//第二种方式
Set entrySet = map.entrySet();
for(Object entry :entrySet){
System.out.println(entry);
}//key和value一起输出
}
HashMap中元素的特点:
- HashMap中所有的key彼此之间是不可重复的、无序的。所有的key就构成另外一个【Set集合】。------->key所在类要重写
hashCode()
和equals()
- HashMap中所有的value彼此之间是可重复的、无序的。所有的value就构成了一个【Collection集合】。-------->value所在的类要重写
equals()
- HashMap中的一个key-value,就构成了一个entry
- HashMap中所有的entry彼此之间是不可重复的、无序的。所有的entry就构成了一个Set集合
TreeMap、Properties
TreeMap的使用:
- 底层使用红黑树存储;
- 可以按照添加的key-value中的key元素的指定的属性大小顺序进行遍历;
- 需要考虑使用(1)自然排序(2)定制排序【具体与Set中的TreeSet排序规则一样】
- 要求:向TreeMap中添加的key必须是用同一类型的对象
Properties:是Hashtable的子类,其key和value都是String类型的,常用来处理属性文件【将数据封装到具体的配置文件中,在程序中读取配置文件中的信息,实现了数据和代码的解耦;由于我们没有修改代码,就省去了重新编译和打包的过程】的,举例代码:
File flie = new File("info.properties");//提前创建好属性配置文件,并且里面写入一些键值对name=Tom,password=123
FileInputStream fis = new FileInputStream(file);//创建输入流对象
Properties pros = new Properties();//创建Properties集合对象
pros.load(fis);//加载流中的文件中的数据,把配置文件中的键值对作为元素读入Properties集合对象pros中
//读取数据
String name = pros.getProperty("name");
String pwd = pros.getProperty("password");
Collections工具类(和Arrays工具类一样)
概述:Collections是一个操作Set、List和Map等集合的工具类 ,和Arrays工具类一样提供许多静态方法用来操作Array对象【Collections工具类用来操作Collecton对象(包括List、Set对象),直接工具类.方法调用】,具体一下常用的方法如下:
Arrays.asList(1,2,3,4,5):
将数组类型转换为List类型的reverse(List):
反转List中元素的顺序shuffle(List):
对List集合元素进行随机排序sort(List):
根据元素的自然排序对指定List集合元素按升序排列sort(List,Comparator){public int compare(Object o1,Object o2){重写compare方法}}:
根据指定的Comparator产生的顺序对List集合元素进行排序swap(List,int i,int j):
将指定List集合中的i处元素与j处元素进行交换- 更对的Collections工具类的方法看Collections工具类的使用
泛型
泛型的理解及其在集合、比较器中的使用
泛型的理解:泛型就是允许在定义类、接口时通过一个“标识”表示类中某个“属性类型”或者是某个方法的‘返回值或者参数的类型’。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的参数类型,也称为类型参数)。
泛型的作用:
//未使用泛型
public void test(){
List list = new ArrayList();
list.add(67);
list.add(71);
list.add("AA");//因为add()的参数是Object类型的,意味着任何类型都可以添加因此不会报错
Iterator iterator = list.iterator();
while(iterator.hasNext()){
Interger = (Iterger)iterator.next();//需要进行强转操作,会报错
int score = i;
System.out.println(score);
}
}
//使用泛型
public void test(){
List<Integer> list = new ArrayList<Integer>();
list.add(67);
list.add(71);
list.add("AA");//报错
Iterator<Interger> iterator = list.iterator();
while(iterator.hasNext()){
Interger = iterator.next();//不需要进行强转操作,会报错
int score = i;
System.out.println(score);
}
}
在Map中使用:
public void test(){
HashMap<String,Integer>map = new HashMap<>;//<>中可省略String和Integer,因为类型推断
map.put("Tom",67);
map.put("Jerry",87);
map.put("Rose",99);
Set<Map.Entry<String,Integer>>entrySet = map.entrySet();
Interator<Map.Entry<String,Integer>>iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry<String,Integer>entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key,value);
}
}
自定义泛型类、泛型方法
自定义泛型类\接口:
- 格式:
class A<T>{T t;public Order(T t,int orderId){ } } ; interface B<T1,T2>{ }
- 注意:
——除创建泛型类对象外,子类继承泛型类、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数【比如public class SubOrder2 extends Order<Integer>
】
——如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数【比如public class SubOrder3<T> extends Order<T>
】
——我们还可以在现有的父类泛型参数的基础上,新增泛型参数【比如public class SubOrder4<E> extends Order<Integer>
和public class SubOrder5<T,E> extends Order<T>
】
——在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但是不可以在静态方法中使用类的泛型
——异常类是不带泛型的
自定义泛型方法:
注意:在泛型类的方法中,使用了类的泛型参数,此方法也不是泛型方法。
格式:public <E>E method(E e){ }
泛型在继承上的体现及通配符的使用
泛型在继承上的体现:
- 类
SuperA
是类A
的父类,则G<SuperA>
和G<A>
是并列的两个类,没有任何字符类的继承关系,也就是不能使用多态性赋值【比如Array<Objectc>、Array<String>
】 - 类
SuperA
是类A
的父类或者接口,SuperA<G>
与A<G>
有继承或实现的关系。即A的实例可以赋值给 SuperA<G>
类型的引用(或变量)【比如List<String>
与ArrayList<String>
】
通配符:? 为了解决泛型子父类继承体现的第一种情况
- 举例:
ArrayList<?>
public void test(){
List<?> list = null;
LIst<Object> list1 = null;
List<String> list2 = null;
list = list1;
list = list2;
method(list1);
method(list2);
}
public void method(List<?> list){
}
说明注意:G<?>
可以看作是G<A>
类型的父类,即可以将G<A>
的对象赋值给G<?>
类型的引用(或变量),而G<?>
读取数据是允许的,而读取的值的类型是Object类型,但是不允许写入数据的
有限制条件的通配符:
——LIst<? extends A>
:可以将List<A>
或LIst<B>
赋值给List<? extends A>
。其中B类是A类的子类。
——LIst<? super A>
:可以将List<A>
或LIst<B>
赋值给List<? extends A>
。其中B类是A类的父类。
数据结构与集合源码
红黑树
描述:即Red-Black Tree。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。红黑树是一种自平衡二叉查找树,是在计算机科学应用中用到的一种数据结构,它是在1972年由Rudolf Bayer发明的。红黑树是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是最高效的:它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
红黑树的特性:
- 每个节点是红色或者黑色
- 根节点是黑色
- 每个叶子节点(NIL)是黑色的。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点)
- 每个红色节点的两个子节点都是黑色的。(从每个叶子节点到根节点所有的路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点(确保没有一条路径会比其他路径长处2倍)
所以当我们插入或者删除节点时,可能会破坏已有的红黑树,使得它不满足以上五个条件,那么此时就需要进行处理【将某个节点变红或变黑;将红黑树某些节点分支进行旋转】,使得它继续满足以上五个条件
详细说明请看红黑树原理简单解析
ArrayList、Vector、LinkedList、HashMap、 LinkedHashMap、HashSet、LinkedHashSet的底部实现
File类与IO流
File类的实例化与方法
File类的理解:File类位于java.io包下,本章中涉及的相关流也都声明在java.io包下;File类的一个对象,对应操作系统下的一个文件或一个文件目录(或文件夹)
构造器:
File file = new File("文件名")
;只是创建了File的对象,并把文件名作为字符串赋给了file,名字为 “文件名” 的文件并不一定存在
public File(String pathname)
:以pathname
为路径创建File对象【创建的可以是文件或文件目录】,可以是绝对路径或者相对路径(绝对路径:以windows操作系统为例,包括盘符在内的文件或文件目录的完整路径。相对路径:相对于某一个文件目录来讲的相对的位置)public File(String parent,String child)
:以parent为父路径【只能是文件目录】,child为子路径【既能是文件目录也能是文件】创建File对象- public File(File parent,String child):根据一个父File对象【只能是文件目录】和子文件路径【既能是文件目录也能是文件】创建File对象
方法:
public String getName()
:获取名称public String getPath()
:获取路径public String getAbsolutePath()
:获取绝对路径public File getAbsoluteFile()
:获取绝对路径表示的文件public String getParent()
:获取上层文件目录路径。若无,则返回nullpublic long length()
:获取文件长度(字节数)public long lastModified()
:获取最后一次修改的时间(毫秒值)public String[ ] list()
:返回一个String数组,表示该File目录中的所有子文件或目录public File[ ] listFiles()
:返回一个File数组,表示该File目录中的所有子文件或目录public boolean renameTo(File dest)
:把文件名重命名为指定的文件路径,例如:file1.renameTo(file2)
【file1必须要存在,且file2必须不存在,且file2所在的文件目录需要存在】public boolean exists()
:此File表示的文件或目录是否实际存在public boolean isDirectory()
:此File表示的是否为目录public boolean isFile()
:此File表示的是否为文件public boolean canRead()
:判断是否可读public boolean canWrite()
:判断是否可写public boolean isHidden()
:判断是否隐藏public boolean createNewFile()
:创建文件,若文件存在,则不创建,返回falsepublic boolean mkdir()
:创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。public boolean exists()
:创建文件目录。如果上层文件目录不存在,一并创建。public boolean exists()
:删除文件或者文件夹
IO流的概述与流的分类
输入与输出:一定要站在程序(内存)的角度去看输入与输出
- 输入input:读取外部数据【文件】(磁盘、光盘等存储设备的数据)到程序(内存)中。
- 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备【文件】中
流的分类:
- 按数据的流向不同分为:输入流和输出流
——输入流:把数据从其他设备【文件】上读取到内存中的流(以InputStream、Reader结尾)
——输出流:把数据从内存中写出到其他设备【文件】上的流(以OutpuStream、Writer结尾) - 按操作数据单位的不同分为:字节流和字符流
——字节流:以字节为单位,读写数据的流(以InputStream、OutputStream结尾)
——字符流:以字符为单位,读写数据的流(以Reader、Writer结尾)
使用FileReader、FileWriter读取、写出文本数据
- FileReader的使用:具体代码实现及要求如下:
输入流对象中read()与read(char [ ] cbuffer)的用法:read()
返回的是字符的ASCII码值,是int类型;read(char[ ] cbuffer)
返回的是从文件输入到数组中字符的个数,若没有了则-1返回
public void test(){
FileReader fr = null;//为了资源的关闭提前声明流的对象
try{//因为涉及到资源的关闭,就必须要解决异常
File file = new File("hello.text");//创建文件对象
fr = new FileReader(file);//创建输入流的对象
……………………………………………………方式1用read()读取…………………………………………………………
int data;//因为read()返回的是整型,所以data需要接收read()的返回值
while(data = fr.read())!=-1){//文件字符的末位是-1
System.out.print((char)data);//因为返回的是int类型,所以需要强制类型转换
………………………………………………方式2用read(char[ ] cbuffer)读取……………………………………………………
char[ ] cbuffer = new char[5];
int len;
while((len = fr.read(cbuffer))!=-1){//read(char[ ] cbuffer)返回的是从文件输入到数组中字符的个数,若没有了则-1返回
//遍历数组
for(i=0;i<=cbuffer.len;i++){
System.out.println(cbuffer[i]);
}
}
}
}catch(IOException e){
e.printStackTrace();
}
finally{
try{
fr.close();//因为close()方法会抛出异常
}catch(IOException e){
e.printStackTrace();
}
}
}
}
- FileWriter的使用:具体代码实现及要求如下:
FileWriter构造器:覆盖文件,使用的构造器:fw = new FileWriter(file);fw = new FileWriter(file,false);
在现有的文件基础上,追加内容使用的构造器fw = new FileWriter(file,ture);
public void test(){
FileWriter fw = null;//为了资源的关闭提前声明流的对象
try{//因为涉及到资源的关闭,就必须要解决异常
File file = new File("info.text");//创建文件对象
//覆盖文件,使用的构造器:
fw = new FileWriter(file);//创建输出流的对象
fw = new FileWriter(file,false);
//在现有的文件基础上,追加内容使用的构造器
fw = new FileWriter(file,ture);
fw.write("I love you");
}catch(IOException e){
e.printStackTrace();
}
finally{
try{
fw.close();//因为close()方法会抛出异常
}catch(IOException e){
e.printStackTrace();
}
}
}
- 复制一份hello.txt文件,命名为hello_copy_txt
write(char[ ] cbuffer,int i,int length):从cbuffer数组的i个位置开始写入长度为length的字符到文件中
public void test(){
//创建File类的对象
FileReader fr = null;
FileWriter fw = null;
try{
File srcFile = new File("hello.text");
Fiel destFile = new File("hello_copy.text");
//创建输入流、输出流
FileReader fr = new FileReader(srcFile);
FileWriter fw = new FileWriter(destFile);
//数据的读入和写出的过程
char[ ] cbuffer = new char[5];
int len;
while((len = fr.read(cbuffer))!=-1){
fw.write(cbuffer,0,len);
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(fr!=null){
fr.close();
}catch(IOException e){
e.printStackTrace();
}
}
try{
if(fr!=null){
fw.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
FileInputStream和FileOutputStream的使用
具体代码实现及要求如下:
public class FileStreamTest{
//创建相关的File类的对象
FileInputStream fis = null;
FileOutputSream fos = null;
try{
File srcFile = new File("playgirl.jpg");
File destFile = new File"palygirl_copy.jpg");
//创建相关的字节流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputSream fos = new FileOutputStream(destFile);
//数据的流入与写出
byte[ ] buffer = new byte[1024];
int len;
while1((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();
}
}
}
缓冲流的使用
缓冲流BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter的作用:提升文件读写效率【创建字符缓冲区,把传输的数据积累到一定程度一次性给内存,减少交互的次数,提升效率】
- 处理非文本文件的字节流:
——BufferedInputStream:read(byte[ ] buffer)
——BufferedOutputStream:write(byte[ ] buffer,0,len)
使用:需要创建缓冲流对象并包在字节流的外层【将字节流作为参数构造缓冲流对象】,并且资源的关闭只需要关闭外层的缓冲流即可
public class FileStreamTest{
//创建相关的File类的对象
BufferedInputStream bis = null;
BufferedOutputSream bos = null;
try{
File srcFile = new File("playgirl.jpg");
File destFile = new File"palygirl_copy.jpg");
//创建相关的字节流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputSream fos = new FileOutputStream(destFile);
//创建缓冲流,需要在字节流的外面再包一层
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//数据的流入与写出
byte[ ] buffer = new byte[1024];
int len;
while1((len = fis.read(buffer))!=-1){
fos.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();
}
}
}
- 处理文本文件的字符流:
——BufferedReader:read(char[ ] cBuffer)/readLine()
——BufferedWriter:write(char[ ] cBuffer,0,len)
readLine():readLine()
返回的一行数据且没有换行符,返回值类型是String;要是末尾了则返回null
public void test(){
//创建File类的对象
BufferedReader br = null;
BufferedWriter bw = null;
try{
File srcFile = new File("hello.text");
Fiel destFile = new File("hello_copy.text");
//创建输入流、输出流
FileReader fr = new FileReader(srcFile);
FileWriter fw = new FileWriter(destFile);
//创建缓冲流
BufferedReader br = new BufferedReader(fr);
BufferedWriter bw = new BufferedWriter(fw);
//数据的读入和写出的过程
String data;
while((data = br.readLine())!=null){
bw.write(data);//readLine返回的一行数据且没有换行符,返回值类型是String;要是末尾了则返回null
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(fr!=null){
fr.close();
}catch(IOException e){
e.printStackTrace();
}
}
try{
if(fr!=null){
fw.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
转换流的使用与各种字符集
字符编码:字符、字符串、字符数组---->字节、字节数组(从我们看得懂得---->我们看不懂的)
字符解码:字节、字节数组---->字符、字符串、字符数组(从我们看不懂得---->我们能看懂的)
注意:解码时使用得字符集必须与当初编码时使用得字符集得相同
转换流:
- 作用:实现字节与字符之间的转换
- API:
——InputStreamReader:将一个输入型的字节流【文件中都是以字节存在的】转换为输入型的字符流【在控制台上以看得懂的字符显示出来】。
使用:需要创建转换流对象并包在字节流的外层【将字节流作为参数构造缓冲流对象】,并且资源的关闭只需要关闭外层的转换流即可;InputStreamReader类对象调用read(char [ ] cBuffer)返回的是读入到cBuffer字符的个数,即InputStreamReader是Reader的子类
public void test(){
//创建File对象
File file = new File("dbcp_utf-8.txt");
//创建输入流对象
FileInputStream fis = new FileInputStream(file);
//创建转换流对象
InputStreamReader isr = new InputStreamReader(fis,"gbk");//显示的使用gbk的字符集
//读入操作
char[ ] cBuffer = new char[1024];
int len;
while((len =isr.read(cBUffer))!=-1){
String str = new String(cBuffer,0,len);
System.out.println(str);
}
ier.close();
}
结果:因为解码集与编码集不兼容,故会出现乱码
——OutputStreamWriter:将一个输出型的字符流转换为输出型的字节流。
使用方式与上面差不多
对象流的使用与对象的序列化机制
数据流及其作用:
- DataOutputStream:可以将内存中的基本数据类型的变量、String类型的变量写出到具体的文件当中
- DataInputStream:可以将文件中保存的数据还原为内存中的基本数据类型的变量、String类型的变量
对象流及其作用:
- ObjectInputStream、ObjectOutputStream:需要创建对象流对象并包在字节流的外层【将字节流对象作为参数构造对象流对象】,并且资源的关闭只需要关闭外层的对象流即可;
作用:可以读写基本数据类型的变量、引用数据类型的变量
对象的序列化机制:对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其他程序获得这种二进制流,就可以恢复成原来的Java对象。
- 序列化过程:使用ObjectOutputStream流实现。将内存中的Java对象保存在文件中或通过网络传输出去
- 反序列化过程:使用ObjectInputStream流实现。将文件中的数据或网络传输过来的数据还原为内存中的Java对象。
//序列化过程
public void test(){
File file = new File("object.text");
ObjectOutputStreaam oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeUTF("江山如此多娇,引无数英雄尽折腰");
oos.flush();//刷新内存
oos.writeObject("轻轻地我走了,正如我轻轻地来");//写入一个Object对象
oos.flush();//刷新内存
oos.close();
}
//反序列化过程
public void test1(){
File file = new File("object.text");
ObjectInputStreaam ois = new ObjectInputStream(new FileInputStream(file));
String str1 = ois.readUFT();
System.out.println(str1);
String str2 = (String)ois.readObject();
System.out.println(str2);
ois.close();
}
自定义类要想实现序列化机制,需要满足:
- 自定义类需要实现接口:Serializable不需要重写方法
- 要求自定义类声明一个全局常量:static final long serialVersionUID = 422344234L;用来唯一标识当前的类。
- 要求自定义类的各个属性也必须是可序列化的。
——对于基本数据类型的属性:默认就是可以序列化的。
——对于引用数据类型的属性:要求实现Serializable接口不需要重写方法
注意点:
- 如果不声明全局变量serialVersionUID,系统会自动声明生成一个针对于当前类的serialVersionUID。如果修改此类的话,会导致serialVersionUID变化,进而导致反序列化时,出现InvalidClassException异常
- 类中的属性如果声明为transient或static,则不会实现序列化。
网络编程
网络编程三要素与InetAddress类
三要素:(1)、使用IP地址(准确地定位网络上的一台或多台主机)(2)、使用端口号(定位主机上的特定的应用)(3)、规范网络通信协议(可靠、高效地进行数据传输)
网络层次:物理层、数据链路层、网络层、传输层、应用层
InetAddress类:
- 作用:InetAddress类的一个实例就代表一个具体的ip地址。
- 实例化方式:
InetAddress getByName(String host)//host指的是ip地址字符串或者是域名,用来获取指定ip对应的InetAddress实例
;InetAddress getLocalHost()//获取本地ip对应的InetAddress的实例
:单例设计模式饿汉式返回实例 - 常用方法:
getHostName()
:获取域名,要是没有域名则获取ip地址;getHostAddress()
:获取ip地址
TCP与UDP协议剖析与TCP编程
TCP协议(传输控制协议):
- TCP协议进行通信的两个应用进程:客户端、服务端
- 使用TCP协议前,必须要建立TCP连接,形成基于字节流的传输数据通道
- 传输前,采用”三次握手“方式,点对点通信,是可靠的【TCP协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有受到另一个通信实体确认信息,则会再次重复刚才发送的消息】
- 在连接中可进行大数据量的传输
- 传输完毕,需要释放已建立的连接,效率低
UDP协议(用户数据报协议):
- UDP协议进行通信的两个应用进程:发送端、接收端。
- 将数据、源、目的封装成数据包(传输的基本单位),不需要建立连接
- 发送不管对方是否准备好,接受方收到也不确认,不能保证数据的完整性,故是不可靠的
- 每个数据报大小限制在64kb内
- 发送数据结束时无需释放资源、开销小、通信效率高
- 适用场景:音频、视频和普通数据的传输。例如视频会议
Socker类:网络上具有唯一标识的ip地址和端口号组合在一起构成唯一能够识别的标识符套接字(Socket)。
基于TCP的Socket通信:
public class TCPTest1{
//客户端
public void client(){
Socket socket = null;
OutputStream os = null;
try{
//创建一个Socket
InetAddress inetAddress = InetAddress.getByName("192.168.21.107");//指明服务器的IP地址
int port = 8989;//声明服务器端口号
Socket socket = new Socket(inetAddress,port);
//发送数据
OutputStream os = socket.getOutputStream();//我们是站在客户端角度的
os.write("你好!".getBytes());//write的参数是字节流
}catch(IOException e){
e.printStackTrace();
}finally{
//关闭socket
try{
if(socket !=null)
socket.close();
}catch(IOException e){
e.printStackTrace();
}
try{
if(socket !=null)
os.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
//服务端
public void server(){
//创建一个ServerSocket
int port = 8989;
ServerSocket serverSocket = new ServerSocket(port);
//调用accept(),接收客户端的Socket
Socket socket = serverSocket.accept();//阻塞式的方法
//接受数据
InputStream is = socket.getInputStream();
byte[ ] buffer = new byte[5];
int len;
ByteArrayOutputStream baos = new ByteArrayOutputStream();//内部维护了一个byte[ ],等buffe数组把所有数据都搬到这个内部数组再一次性发出去
while((len = is.read(buffer))!-1){
//错误的,可能出现乱码,因为buffer数组太小,可能把汉字分开翻译
//String str = new String(buffer,0,len);
//System.out.println(str);
//正确的
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
socket.close();
serverSocket.close();
is.close();
}
//关闭Socket、ServerSocket
}
}
聊天室代码实现
具体的 实现代码见聊天室代码尚硅谷康老师讲解
UDP、URL网络编程
//发送端
public void sender(){
//创建DatagramSocket的实例
DatagramSocket ds = new DatagramSocket();
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
int port = 9090;
byte[ ] bytes = "我是发送端".getBytes("utf-8");
DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,inetAddress,port);
//发送数据
ds.send();
ds.close();
}
//接收端
public void receiver(){
//创建DatagramSocket的实例
int port = 9090;
DatagramSocket ds = new DatagramSocket(port);
//创建数据报的对象,用于接收发送端发送过来的数据
byte [] buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer,length);
//接收数据
ds.receive(packet);
//获取数据,并打印到控制台上
String str = new String(packet.getData(),0,packet.length())
System.out.println(str);
ds.close();
}
URL(统一资源定位符)
- 作用:一个具体的url就对应着互联网某一资源的地址。
- URL的格式:http:【应用层协议】//192.168.21.107【ip地址】:8080【端口号】/examples/abcd.jpg?name=Tom【参数列表,服务器下资源的所在位置】
- URL类的实例化及常用方法
String str ="http://192.168.21.107:8080/examples/abcd.jpg?name=Tom"; URL url = new URL(str);
方法可查询 下载指定的URL的资源到本地
:
public void test(){
URL url = new URL("http://127.0.0.1:8080/examples/abcd.jpg");//获取资源所在的url地址
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();//连接服务器与客户端
InputStream is = urlConnection.getInputStrean();//从服务器端获取输入流
File file = new File(dest.jpg);
FileOutputStream fos = new FileOutputStream(file);//获取输出流,准备将从服务器输入到内存中的资源输出到文件中
byte[ ] buffer = new byte[1024];
int len;
while((len = is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
urlConnection.disconnecct();
}