为您推荐:
精华内容
最热下载
问答
  • 5星
    200.92MB SKCQTGZX 2021-09-03 15:17:46
  • 309KB weixin_38680247 2020-10-15 22:29:00
  • 在本文将深入展开在面试过程中操作系统部分的知识,用最简短的篇章深入理解。 参考资料: 《Linux+C程序设计大全》第四版,清华大学出版社,配套源码:linux_c_program_design 《后台开发:核心技术与应用实践》 ...

     

    前言

    在本文将深入展开在面试过程中操作系统部分的知识,用最简短的篇章深入理解。

    参考资料:

    • 《Linux+C程序设计大全》第四版,清华大学出版社,配套源码:linux_c_program_design
    • 《后台开发:核心技术与应用实践》
    • 《操作系统》清华大学(向勇、陈渝) ,在线课程
    • 部分信息来自:CyC2018/CS-Notes

    一、概述

    1. 操作系统基本特征

    1. 并发

    并发是指宏观上在一段时间内能同时运行多个程序,而并行则指同一时刻能运行多个指令。

    并行需要硬件支持,如多流水线或者多处理器。

    操作系统通过引入进程和线程,使得程序能够并发运行。

    2. 共享

    共享是指系统中的资源可以被多个并发进程共同使用。

    有两种共享方式:互斥共享和同时共享。

    互斥共享的资源称为临界资源,例如打印机等,在同一时间只允许一个进程访问,需要用同步机制来实现对临界资源的访问。

    3. 虚拟

    虚拟技术把一个物理实体转换为多个逻辑实体。

    利用多道程序设计技术,让每个用户都觉得有一个计算机专门为他服务。

    主要有两种虚拟技术:时分复用技术空分复用技术。例如多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。

    4. 异步

    异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。

    但只要运行环境相同,OS需要保证程序运行的结果也要相同。

    2. 操作系统基本功能

    1. 进程管理

    进程控制、进程同步、进程通信、死锁处理、处理机调度等。

    2. 内存管理

    内存分配、地址映射、内存保护与共享、虚拟内存等。

    3. 文件管理

    文件存储空间的管理、目录管理、文件读写管理和保护等。

    4. 设备管理

    完成用户的 I/O 请求,方便用户使用各种设备,并提高设备的利用率。

    主要包括缓冲管理、设备分配、设备处理、虛拟设备等。

    3. 系统调用

    如果一个进程在用户态需要使用内核态的功能,就进行系统调用从而陷入内核,由操作系统代为完成。

    Linux 的系统调用主要有以下这些:

    TaskCommands
    进程控制fork(); exit(); wait();
    进程通信pipe(); shmget(); mmap();
    文件操作open(); read(); write();
    设备操作ioctl(); read(); write();
    信息维护getpid(); alarm(); sleep();
    安全chmod(); umask(); chown();

    4. 大内核和微内核

    1. 大内核

    大内核是将操作系统功能作为一个紧密结合的整体放到内核。

    由于各模块共享信息,因此有很高的性能。

    2. 微内核

    由于操作系统不断复杂,因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。

    在微内核结构下,操作系统被划分成小的、定义良好的模块,只有微内核这一个模块运行在内核态,其余模块运行在用户态。

    因为需要频繁地在用户态和核心态之间进行切换,所以会有一定的性能损失。

     

    5. 中断分类

    1. 外中断

    由 CPU 执行指令以外的事件引起,如 I/O 完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。

    2. 异常

    由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。

    3. 陷入

    在用户程序中使用系统调用。

    类型源头响应方式处理机制
    中断外设异步持续,对用户应用程序是透明的
    异常应用程序意想不到的行为同步杀死或重新执行意想不到的应用程序指令
    系统调用应用程序请求操作提供服务异步或同步等待和持续

    6. 什么是堆和栈?说一下堆栈都存储哪些数据?

    栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。

    数据结构中这两个完全就不放一块来讲,数据结构中栈和队列才是好基友,我想新手也很容易区分。

    我想需要区分的情况肯定不是在数据结构话题下,而大多是在 OS 关于不同对象的内存分配这块上。

    简单讲的话,在 C 语言中:

    int a[N];   // go on a stack
    int* a = (int *)malloc(sizeof(int) * N);  // go on a heap


    7. 如何理解分布式锁?

    • 分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

    二、进程管理

    1. 进程与线程

     

    1. 进程

    进程是资源分配的基本单位,用来管理资源(例如:内存,文件,网络等资源)

    进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。(PCB是描述进程的数据结构)

    下图显示了 4 个程序创建了 4 个进程,这 4 个进程可以并发地执行。

     

    2. 线程

    线程是独立调度的基本单位。

    一个进程中可以有多个线程,它们共享进程资源。

    QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。

    3. 区别

    (一)拥有资源

    进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。

    (二)调度

    线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。

    (三)系统开销

    由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。

    (四)通信方面

    进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信。

    2. 进程状态的切换(生命周期)

    • 就绪状态(ready):等待被调度
    • 运行状态(running)
    • 阻塞状态(waiting):等待资源

    应该注意以下内容:

    • 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
    • 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。
    • 进程只能自己阻塞自己,因为只有进程自身才知道何时需要等待某种事件的发生

    3. 进程调度算法

    网易有道面经

    不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法。

    1. 批处理系统

    批处理系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。

    1.1 先来先服务

    先来先服务 first-come first-serverd(FCFS)

    按照请求的顺序进行调度。

    有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。

    1.2 短作业优先

    短作业优先 shortest job first(SJF)

    按估计运行时间最短的顺序进行调度。

    长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。

    1.3 最短剩余时间优先

    最短剩余时间优先 shortest remaining time next(SRTN)

    按估计剩余时间最短的顺序进行调度。

    2. 交互式系统

    交互式系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。

    2.1 时间片轮转

    将所有就绪进程按 FCFS (先来先服务) 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。

    时间片轮转算法的效率和时间片的大小有很大关系。因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。

     

    2.2 优先级调度

    为每个进程分配一个优先级,按优先级进行调度。

    为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。

    2.3 多级反馈队列

    如果一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。

    多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。

    每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。

    可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。

     

    3. 实时系统

    实时系统要求一个请求在一个确定时间内得到响应。

    分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。

    参考资料:

    4. 进程同步

    1. 临界区

    对临界资源进行访问的那段代码称为临界区

    为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。

    // entry section
    // critical section;
    // exit section

    2. 同步与互斥

    • 同步:多个进程按一定顺序执行;
    • 互斥:多个进程在同一时刻只有一个进程能进入临界区。

    3. 信号量

    P 和 V 是来源于两个荷兰语词汇,P() ---prolaag (荷兰语,尝试减少的意思),V() ---verhoog(荷兰语,增加的意思)

    信号量(Semaphore)是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。

    • down : 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0,进程睡眠,等待信号量大于 0;(阻塞)
    • up :对信号量执行 +1 操作,唤醒睡眠的进程让其完成 down 操作。(唤醒)

    down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。

    如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁。

    typedef int semaphore;
    semaphore mutex = 1;
    void P1() {
        down(&mutex);
        // 临界区
        up(&mutex);
    }
    
    void P2() {
        down(&mutex);
        // 临界区
        up(&mutex);
    }

    使用信号量实现生产者-消费者问题

    问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。

    因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。

    为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。其中,empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才可以放入物品;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走物品。

    注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,也就无法执行 up(empty) 操作,empty 永远都为 0,那么生产者和消费者就会一直等待下去,造成死锁。

    #define N 100
    typedef int semaphore;
    semaphore mutex = 1;
    semaphore empty = N;
    semaphore full = 0;
    
    void producer() {
        while(TRUE){
            int item = produce_item(); // 生产一个产品
            // down(&empty) 和 down(&mutex) 不能交换位置,否则造成死锁
            down(&empty); // 记录空缓冲区的数量,这里减少一个产品空间
            down(&mutex); // 互斥锁
            insert_item(item);
            up(&mutex); // 互斥锁
            up(&full); // 记录满缓冲区的数量,这里增加一个产品
        }
    }
    
    void consumer() {
        while(TRUE){
            down(&full); // 记录满缓冲区的数量,减少一个产品
            down(&mutex); // 互斥锁
            int item = remove_item();
            up(&mutex); // 互斥锁
            up(&empty); // 记录空缓冲区的数量,这里增加一个产品空间
            consume_item(item);
        }
    }

    4. 管程

    管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。

    使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。

    管程是为了解决信号量在临界区的 PV 操作上的配对的麻烦,把配对的 PV 操作集中在一起,生成的一种并发编程方法。其中使用了条件变量这种同步机制。

    c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法,客户端代码通过调用这两个方法来解决生产者-消费者问题。

    monitor ProducerConsumer
        integer i;
        condition c;
    
        procedure insert();
        begin
            // ...
        end;
    
        procedure remove();
        begin
            // ...
        end;
    end monitor;

    管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否者其它进程永远不能使用管程。

    管程引入了 条件变量 以及相关的操作:wait() 和 signal() 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。

    使用管程实现生产者-消费者问题

    // 管程
    monitor ProducerConsumer
        condition full, empty;
        integer count := 0;
        condition c;
    
        procedure insert(item: integer);
        begin
            if count = N then wait(full);
            insert_item(item);
            count := count + 1;
            if count = 1 then signal(empty);
        end;
    
        function remove: integer;
        begin
            if count = 0 then wait(empty);
            remove = remove_item;
            count := count - 1;
            if count = N -1 then signal(full);
        end;
    end monitor;
    
    // 生产者客户端
    procedure producer
    begin
        while true do
        begin
            item = produce_item;
            ProducerConsumer.insert(item);
        end
    end;
    
    // 消费者客户端
    procedure consumer
    begin
        while true do
        begin
            item = ProducerConsumer.remove;
            consume_item(item);
        end
    end;

    5. 经典同步问题

    生产者和消费者问题前面已经讨论过了。

    1. 读者-写者问题

    允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。读者优先策略

    Rcount:读操作的进程数量(Rcount=0)

    CountMutex:对于Rcount进行加锁(CountMutex=1)

    WriteMutex:互斥量对于写操作的加锁(WriteMutex=1)

    Rcount = 0;
    semaphore CountMutex = 1;
    semaphore WriteMutex = 1;
    
    void writer(){
        while(true){
            sem_wait(WriteMutex);
            // TO DO write();
            sem_post(WriteMutex);
        }
    }
    
    // 读者优先策略
    void reader(){
        while(true){
            sem_wait(CountMutex);
            if(Rcount == 0)
                sem_wait(WriteMutex);
            Rcount++;
            sem_post(CountMutex);
            
            // TO DO read();
            
            sem_wait(CountMutex);
            Rcount--;
            if(Rcount == 0)
                sem_post(WriteMutex);
            sem_post(CountMutex);
    	}
    }
    

    2. 哲学家进餐问题

     

    五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。

    **方案一:**下面是一种错误的解法,考虑到如果所有哲学家同时拿起左手边的筷子,那么就无法拿起右手边的筷子,造成死锁。

    #define N 5		   // 哲学家个数
    void philosopher(int i)  // 哲学家编号:0 - 4
    {
        while(TRUE)
        {
            think();			// 哲学家在思考
            take_fork(i);			// 去拿左边的叉子
            take_fork((i + 1) % N);	// 去拿右边的叉子
            eat();				// 吃面条中….
            put_fork(i);			// 放下左边的叉子
            put_fork((i + 1) % N);	// 放下右边的叉子
        }
    }
    

    方案二:对拿叉子的过程进行了改进,但仍不正确

    #define N 5	 // 哲学家个数
    while(1)  // 去拿两把叉子
    {       
        take_fork(i);			// 去拿左边的叉子
        if(fork((i+1)%N)) {		// 右边叉子还在吗
        	take_fork((i + 1) % N);// 去拿右边的叉子
        	break;			// 两把叉子均到手
        }
        else {				// 右边叉子已不在
        	put_fork(i);		// 放下左边的叉子
        	wait_some_time();	// 等待一会儿
        }
    }
    

    方案三:等待时间随机变化。可行,但非万全之策

    #define N 5	 // 哲学家个数
    while(1)  // 去拿两把叉子
    {       
    	take_fork(i);			// 去拿左边的叉子
    	if(fork((i+1)%N)) {		// 右边叉子还在吗
    	    take_fork((i + 1) % N);// 去拿右边的叉子
    	    break;			// 两把叉子均到手
    	}
    	else {				// 右边叉子已不在
    	    put_fork(i);		// 放下左边的叉子
    	    wait_random_time( );	// 等待随机长时间
    	}
    }
    

    方案四:互斥访问。正确,但每次只允许一人进餐

    semaphore mutex	   // 互斥信号量,初值1
    void philosopher(int i)  // 哲学家编号i:0-4	
    {
    	while(TRUE){
    	    think();			// 哲学家在思考
    	    P(mutex);			// 进入临界区
    	    take_fork(i);			// 去拿左边的叉子
    	    take_fork((i + 1) % N);	// 去拿右边的叉子
    	    eat();				// 吃面条中….
    	    put_fork(i);			// 放下左边的叉子
    	    put_fork((i + 1) % N);	// 放下右边的叉子
    	    V(mutex);			// 退出临界区
    	}
    }
    

    正确方案如下:

    为了防止死锁的发生,可以设置两个条件(临界资源):

    • 必须同时拿起左右两根筷子;
    • 只有在两个邻居都没有进餐的情况下才允许进餐。
    //1. 必须由一个数据结构,来描述每个哲学家当前的状态
    #define N 5
    #define LEFT i // 左邻居
    #define RIGHT (i + 1) % N    // 右邻居
    #define THINKING 0
    #define HUNGRY   1
    #define EATING   2
    typedef int semaphore;
    int state[N];                // 跟踪每个哲学家的状态
    
    //2. 该状态是一个临界资源,对它的访问应该互斥地进行
    semaphore mutex = 1;         // 临界区的互斥
    
    //3. 一个哲学家吃饱后,可能要唤醒邻居,存在着同步关系
    semaphore s[N];              // 每个哲学家一个信号量
    
    void philosopher(int i) {
        while(TRUE) {
            think();
            take_two(i);
            eat();
            put_tow(i);
        }
    }
    
    void take_two(int i) {
        P(&mutex);  // 进入临界区
        state[i] = HUNGRY; // 我饿了
        test(i); // 试图拿两把叉子
        V(&mutex); // 退出临界区
        P(&s[i]); // 没有叉子便阻塞
    }
    
    void put_tow(i) {
        P(&mutex);
        state[i] = THINKING;
        test(LEFT);
        test(RIGHT);
        V(&mutex);
    }
    
    void test(i) {         // 尝试拿起两把筷子
        if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {
            state[i] = EATING;
            V(&s[i]); // 通知第i个人可以吃饭了
        }
    }

    6. 进程通信

    进程同步与进程通信很容易混淆,它们的区别在于:

    • 进程同步:控制多个进程按一定顺序执行
    • 进程通信:进程间传输信息

    进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。

    * 进程通信方式

     

    直接通信

    发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息。

    Send 和 Receive 原语的使用格式如下:

    Send(Receiver,message);//发送一个消息message给接收进程Receiver
    Receive(Sender,message);//接收Sender进程发送的消息message

    间接通信

    间接通信方式是指进程之间的通信需要通过作为共享数据结构的实体。该实体用来暂存发送进程发给目标进程的消息。

    发送进程把消息发送到某个中间实体中,接收进程从中间实体中取得消息。这种中间实体一般称为信箱,这种通信方式又称为信箱通信方式。该通信方式广泛应用于计算机网络中,相应的通信系统称为电子邮件系统。

    1. 管道

    管道是通过调用 pipe 函数创建的,fd[0] 用于读,fd[1] 用于写。

    #include <unistd.h>
    int pipe(int fd[2]);

    它具有以下限制:

    • 只支持半双工通信(单向传输);
    • 只能在父子进程中使用。

    2. 命名管道

    也称为命名管道,去除了管道只能在父子进程中使用的限制。

    #include <sys/stat.h>
    int mkfifo(const char *path, mode_t mode);
    int mkfifoat(int fd, const char *path, mode_t mode);

    FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。

    3. 消息队列

    间接(内核)

    相比于 FIFO,消息队列具有以下优点:

    • 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
    • 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
    • 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。

    4. 信号量

    它是一个计数器,用于为多个进程提供对共享数据对象的访问。

    5. 共享内存

    允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。

    需要使用信号量用来同步对共享存储的访问。

    多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用使用内存的匿名段。

    6. 套接字

    与其它通信机制不同的是,它可用于不同机器间的进程通信。

    7. 线程间通信和进程间通信

    线程间通信

    • synchronized同步

      • 这种方式,本质上就是 “共享内存” 式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
    • while轮询的方式

      • 在这种方式下,ThreadA 不断地改变条件,ThreadB 不停地通过 while 语句检测这个条件 (list.size()==5) 是否成立 ,从而实现了线程间的通信。但是这种方式会浪费 CPU 资源。
      • 之所以说它浪费资源,是因为 JVM 调度器将 CPU 交给 ThreadB 执行时,它没做啥 “有用” 的工作,只是在不断地测试某个条件是否成立。
      • 就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是:在干别的事情,当有电话来时,响铃通知TA电话来了。
    • wait/notify机制

      • 当条件未满足时,ThreadA 调用 wait() 放弃 CPU,并进入阻塞状态。(不像 while 轮询那样占用 CPU)

        当条件满足时,ThreadB 调用 notify() 通知线程 A,所谓通知线程 A,就是唤醒线程 A,并让它进入可运行状态。

    • 管道通信

      • java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

    进程间通信

    • 管道(Pipe) :管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
    • 命名管道(named pipe) :命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关 系 进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
    • 信号(Signal) :信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身;Linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
    • 消息(Message)队列 :消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
    • 共享内存 :使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
    • 内存映射(mapped memory) :内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
    • 信号量(semaphore) :主要作为进程间以及同一进程不同线程之间的同步手段。
    • 套接口(Socket) :更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:linux和System V的变种都支持套接字。

    参考资料:

    8. 进程操作

    Linux进程结构可由三部分组成:

    • 代码段(程序)
    • 数据段(数据)
    • 堆栈段(控制块PCB)

    进程控制块是进程存在的惟一标识,系统通过PCB的存在而感知进程的存在。系统通过 PCB 对进程进行管理和调度。PCB 包括创建进程、执行进程、退出进程以及改变进程的优先级等。

    一般程序转换为进程分以下几个步骤:

    1. 内核将程序读入内存,为程序分配内存空间
    2. 内核为该进程分配进程标识符 PID 和其他所需资源
    3. 内核为进程保存 PID 及相应的状态信息,把进程放到运行队列中等待执行,程序转化为进程后可以被操作系统的调度程序调度执行了

      在 UNIX 里,除了进程 0(即 PID=0 的交换进程,Swapper Process)以外的所有进程都是由其他进程使用系统调用 fork 创建的,这里调用 fork 创建新进程的进程即为父进程,而相对应的为其创建出的进程则为子进程,因而除了进程 0 以外的进程都只有一个父进程,但一个进程可以有多个子进程。操作系统内核以进程标识符(Process Identifier,即 PID )来识别进程。进程 0 是系统引导时创建的一个特殊进程,在其调用 fork 创建出一个子进程(即 PID=1 的进程 1,又称 init)后,进程 0 就转为交换进程(有时也被称为空闲进程),而进程1(init进程)就是系统里其他所有进程的祖先。

      进程0:Linux引导中创建的第一个进程,完成加载系统后,演变为进程调度、交换及存储管理进程。   进程1:init 进程,由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程。

      Linux中 1 号进程是由 0 号进程来创建的,因此必须要知道的是如何创建 0 号进程,由于在创建进程时,程序一直运行在内核态,而进程运行在用户态,因此创建 0 号进程涉及到特权级的变化,即从特权级 0 变到特权级 3,Linux 是通过模拟中断返回来实现特权级的变化以及创建 0 号进程,通过将 0 号进程的代码段选择子以及程序计数器EIP直接压入内核态堆栈,然后利用 iret 汇编指令中断返回跳转到 0 号进程运行。

    创建一个进程

    进程是系统中基本的执行单位。Linux 系统允许任何一个用户进程创建一个子进程,创建成功后,子进程存在于系统之中,并且独立于父进程。该子进程可以接受系统调度,可以得到分配的系统资源。系统也可以检测到子进程的存在,并且赋予它与父进程同样的权利。

    Linux系统下使用 fork() 函数创建一个子进程,其函数原型如下:

    #include <unistd.h>
    pid_t fork(void);

    在讨论 fork() 函数之前,有必要先明确父进程和子进程两个概念。除了 0 号进程(该进程是系统自举时由系统创建的)以外,Linux 系统中的任何一个进程都是由其他进程创建的。创建新进程的进程,即调用 fork() 函数的进程就是父进程,而新创建的进程就是子进程。

    fork() 函数不需要参数,返回值是一个进程标识符 (PID)。对于返回值,有以下 3 种情况:

    1. 对于父进程,fork() 函数返回新创建的子进程的 ID。

    2. 对于子进程,fork() 函数返回 0。由于系统的 0 号进程是内核进程,所以子进程的进程标识符不会是0,由此可以用来区别父进程和子进程。

    3. 如果创建出错,则 fork() 函数返回 -1。

    fork() 函数会创建一个新的进程,并从内核中为此进程分配一个新的可用的进程标识符 (PID),之后,为这个新进程分配进程空间,并将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段(写时复制)。这时候,系统中又多了一个进程,这个进程和父进程一模一样,两个进程都要接受系统的调度。

    注意:由于在复制时复制了父进程的堆栈段,所以两个进程都停留在了 fork() 函数中,等待返回。因此,fork() 函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。

    下面给出的示例程序用来创建一个子进程,该程序在父进程和子进程中分别输出不同的内容。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    int main(void)
    {
    	pid_t pid; // 保存进程ID
    	pid = fork(); // 创建一个新进程
    	if(pid < 0){ // fork出错
    		printf("fail to fork\n");
    		exit(1);
    	}
        else if(pid == 0){	// 子进程
            // 打印子进程的进程ID
    		printf("this is child, pid is : %u\n", getpid()); 
    	}
        else{
            // 打印父进程和其子进程的进程ID
    		printf("this is parent, pid is : %u, child-pid is : %u\n", getpid(), pid);	
    	}
    	return 0;
    }

    程序运行结果如下:

    $ ./fork 
    Parent, PID: 2598, Sub-process PID: 2599
    Sub-process, PID: 2599, PPID: 2598

    由于创建的新进程和父进程在系统看来是地位平等的两个进程,所以运行机会也是一样的,我们不能够对其执行先后顺序进行假设,先执行哪一个进程取决于系统的调度算法。如果想要指定运行的顺序,则需要执行额外的操作。正因为如此,程序在运行时并不能保证输出顺序和上面所描述的一致。

    getpid() 是获得当前进程的pid,而 getppid() 则是获得父进程的 id。

    父子进程的共享资源

    子进程完全复制了父进程的地址空间的内容,包括堆栈段和数据段的内容。子进程并没有复制代码段,而是和父进程共用代码段。这样做是存在其合理依据的,因为子进程可能执行不同的流程,那么就会改变数据段和堆栈段,因此需要分开存储父子进程各自的数据段和堆栈段。但是代码段是只读的,不存在被修改的问题,因此这一个段可以让父子进程共享,以节省存储空间,如下图所示。

    下面给出一个示例来说明这个问题。该程序定义了一个全局变量 global、一个局部变量 stack 和一个指针 heap。该指针用来指向一块动态分配的内存区域。之后,该程序创建一个子进程,在子进程中修改 global、stack 和动态分配的内存中变量的值。然后在父子进程中分别打印出这些变量的值。由于父子进程的运行顺序是不确定的,因此我们先让父进程额外休眠2秒,以保证子进程先运行。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    // 全局变量,在数据段中
    int global; 
    int main()
    {
    	pid_t pid;
    	int stack = 1; // 局部变量,在栈中
    	int * heap;
    	heap = (int *)malloc(sizeof(int)); // 动态分配的内存,在堆中
    	*heap = 2;
    	pid = fork(); // 创建一个子进程
    	if(pid < 0){ // 创建子进程失败
    		printf("fail to fork\n");
    		exit(1);	
    	}
        else if(pid == 0){ // 子进程,改变各变量的值
    		global++; // 修改栈、堆和数据段
    		stack++;
    		(*heap)++;
    		printf("the child, data : %d, stack : %d, heap : %d\n", global, stack, *heap);
    		exit(0); // 子进程运行结束
    	}
        // 父进程休眠2秒钟,保证子进程先运行
    	sleep(2); 
        // 输出结果
    	printf("the parent, data : %d, stack : %d, heap : %d\n", global, stack, *heap);
    	return 0;
    }

    程序运行效果如下:

    $ ./fork 
    In sub-process, global: 2, stack: 2, heap: 3
    In parent-process, global: 1, stack: 1, heap: 2
    

    由于父进程休眠了2秒钟,子进程先于父进程运行,因此会先在子进程中修改数据段和堆栈段中的内容。因此不难看出,子进程对这些数据段和堆栈段中内容的修改并不会影响到父进程的进程环境。

    fork()函数的出错情况

    有两种情况可能会导致fork()函数出错:

    1. 系统中已经有太多的进程存在了

    2. 调用fork()函数的用户进程太多了

    一般情况下,系统都会对一个用户所创建的进程数加以限制。如果操作系统不对其加限制,那么恶意用户可以利用这一缺陷攻击系统。下面是一个利用进程的特性编写的一个病毒程序,该程序是一个死循环,在循环中不断调用fork()函数来创建子进程,直到系统中不能容纳如此多的进程而崩溃为止。下图展示了这种情况:

    #include <unistd.h>
    int main()
    {
    	while(1)
    		fork(); /* 不断地创建子进程,使系统中进程溢满 */
    	return 0;
    }

    创建共享空间的子进程

    进程在创建一个新的子进程之后,子进程的地址空间完全和父进程分开。父子进程是两个独立的进程,接受系统调度和分配系统资源的机会均等,因此父进程和子进程更像是一对兄弟。如果父子进程共用父进程的地址空间,则子进程就不是独立于父进程的。

    Linux环境下提供了一个与 fork() 函数类似的函数,也可以用来创建一个子进程,只不过新进程与父进程共用父进程的地址空间,其函数原型如下:

    #include <unistd.h>
    pid_t vfork(void);

    vfork() 和 fork() 函数的区别有以下两点:

    1. vfork() 函数产生的子进程和父进程完全共享地址空间,包括代码段、数据段和堆栈段,子进程对这些共享资源所做的修改,可以影响到父进程。由此可知,vfork() 函数与其说是产生了一个进程,还不如说是产生了一个线程。

    2. vfork() 函数产生的子进程一定比父进程先运行,也就是说父进程调用了 vfork() 函数后会等待子进程运行后再运行。

    下面的示例程序用来验证以上两点。在子进程中,我们先让其休眠 2 秒以释放 CPU 控制权,在前面的 fork() 示例代码中我们已经知道这样会导致其他线程先运行,也就是说如果休眠后父进程先运行的话,则第 1 点则为假;否则为真。第 2 点为真,则会先执行子进程,那么全局变量便会被修改,如果第 1 点为真,那么后执行的父进程也会输出与子进程相同的内容。代码如下:

    //@file vfork.c
    //@brief vfork() usage
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int global = 1;
    
    int main(void)
    {
        pid_t pid;
        int   stack = 1;
        int  *heap;
    
        heap = (int *)malloc(sizeof(int));
        *heap = 1;
    
        pid = vfork();
        if (pid < 0)
        {
            perror("fail to vfork");
            exit(-1);
        }
        else if (pid == 0)
        {
            //sub-process, change values
            sleep(2);//release cpu controlling
            global = 999;
            stack  = 888;
            *heap  = 777;
            //print all values
            printf("In sub-process, global: %d, stack: %d, heap: %d\n",global,stack,*heap);
            exit(0);
        }
        else
        {
            //parent-process
            printf("In parent-process, global: %d, stack: %d, heap: %d\n",global,stack,*heap);
        }
    
        return 0;
    }

    程序运行效果如下:

    $ ./vfork 
    In sub-process, global: 999, stack: 888, heap: 777
    In parent-process, global: 999, stack: 888, heap: 777
    

    在函数内部调用vfork

    在使用 vfork() 函数时应该注意不要在任何函数中调用 vfork() 函数。下面的示例是在一个非 main 函数中调用了 vfork() 函数。该程序定义了一个函数 f1(),该函数内部调用了 vfork() 函数。之后,又定义了一个函数 f2(),这个函数没有实际的意义,只是用来覆盖函数 f1() 调用时的栈帧。main 函数中先调用 f1() 函数,接着调用 f2() 函数。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int f1(void)
    {
        vfork();
        return 0;
    }
    
    int f2(int a, int b)
    {
        return a+b;
    }
    
    int main(void)
    {
        int c;
        
        f1();
        c = f2(1,2);
        printf("%d\n",c);
    
        return 0;
    }

    程序运行效果如下:

    $ ./vfork 
    3
    Segmentation fault (core dumped)
    

    通过上面的程序运行结果可以看出,一个进程运行正常,打印出了预期结果,而另一个进程似乎出了问题,发生了段错误。出现这种情况的原因可以用下图来分析一下:

    1534857746158

    左边这张图说明调用 vfork() 之后产生了一个子进程,并且和父进程共享堆栈段,两个进程都要从 f1() 函数返回。由于子进程先于父进程运行,所以子进程先从 f1() 函数中返回,并且调用 f2() 函数,其栈帧覆盖了原来 f1() 函数的栈帧。当子进程运行结束,父进程开始运行时,就出现了右图的情景,父进程需要从 f1() 函数返回,但是 f1() 函数的栈帧已经被 f2() 函数的所替代,因此就会出现父进程返回出错,发生段错误的情况。

    由此可知,使用 vfork() 函数之后,子进程对父进程的影响是巨大的,其同步措施势在必行。

    退出进程

    当一个进程需要退出时,需要调用退出函数。Linux 环境下使用 exit() 函数退出进程,其函数原型如下:

    #include <stdlib.h>
    void exit(int status);

    exit() 函数的参数表示进程的退出状态,这个状态的值是一个整型,保存在全局变量 $ ? 中,在 shell 中可以通过 echo $? 来检查退出状态值。

    注意:这个退出函数会深入内核注销掉进程的内核数据结构,并且释放掉进程的资源。

    exit函数与内核函数的关系

    exit 函数是一个标准的库函数,其内部封装了 Linux 系统调用 exit() 函数。两者的主要区别在于 exit() 函数会在用户空间做一些善后工作,例如清理用户的 I/O 缓冲区,将其内容写入 磁盘文件等,之后才进入内核释放用户进程的地址空间;而 exit() 函数直接进入内核释放用户进程的地址空间,所有用户空间的缓冲区内容都将丢失。

    设置进程所有者

    每个进程都有两个用户 ID,实际用户 ID 和有效用户 ID。通常这两个 ID 的值是相等的,其取值为进程所有者的用户 ID。但是,在有些场合需要改变进程的有效用户 ID。Linux 环境下使用 setuid() 函数改变一个进程的实际用户ID和有效用户ID,其函数原型如下:

    #include <unistd.h>
    int setuid(uid_t uid);

    setuid() 函数的参数表示改变后的新用户 ID,如果成功修改当前进程的实际用户 ID 和有效用户 ID,函数返回值为 0;如果失败,则返回 -1。只有两种用户可以修改进程的实际用户 ID 和有效用户 ID:

    1. 根用户:根用户可以将进程的实际用户 ID 和有效用户 ID 更换。

    2. 其他用户:其该用户的用户 ID 等于进程的实际用户 ID 或者保存的用户 ID。

    也就是说,用户可以将自己的有效用户 ID 改回去。这种情况多出现于下面的情况:一个进程需要具有某种权限,所以将其有效用户 ID 设置为具有这种权限的用户 ID,当进程不需要这种权限时,进程还原自己之前的有效用户 ID,使自己的权限复原。下面给出一个修改的示例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    int main(void)
    {
    	FILE *fp;
    	uid_t uid;
    	uid_t euid;
    	uid = getuid();		/* 得到进程的实际用户ID */
    	euid = geteuid();	/* 得到进程的有效用户ID */
    	printf("the uid is : %d\n", uid);
    	printf("the euid is : %d\n", euid);
    	if(setuid(8000) == -1){ /* 改变进程的实际用户ID和有效用户ID */
    		perror("fail to set uid");
    		exit(1);
    	}
    	printf("after changing\n");
    	uid = getuid();		/* 再次得到进程的实际用户ID */
    	euid = geteuid();	/* 再次得到进程的有效用户ID */
    	printf("the uid is : %d\n", uid);
    	printf("the euid is : %d\n", euid);
    	return 0;
    }

    程序运行效果如下:

    $./setuid
    the uid is : 0
    the euid is : 0
    after changing
    the uid is : 8000
    the euid is : 8000
    

    本节参考:

    9. 孤儿进程和僵尸进程

    基本概念

    我们知道在 Unix/Linux 中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。当一个进程完成它的工作终止之后,它的父进程需要调用 wait() 或者 waitpid() 系统调用取得子进程的终止状态。

    孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为1)所收养,并由 init 进程对它们完成状态收集工作**。**

    僵尸进程:一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。

    问题及危害

      Unix 提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号 the process ID,退出状态 the termination status of the process,运行时间 the amount of CPU time taken by the process 等)。直到父进程通过 wait / waitpid 来取时才释放。但这样就导致了问题,如果进程不调用 wait / waitpid 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。此即为僵尸进程的危害,应当避免。

      孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了 init 进程身上,init 进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为 init,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

      任何一个子进程(init除外)在exit() 之后,并非马上就消失掉,而是留下一个称为僵尸进程 (Zombie) 的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用 ps 命令就能看到子进程的状态是 Z。如果父进程能及时处理,可能用 ps 命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。如果父进程在子进程结束之前退出,则子进程将由 init 接管。 init 将会以父进程的身份对僵尸状态的子进程进行处理。

      僵尸进程危害场景:

      例如有个进程,它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用 ps 命令查看的话,就会看到很多状态为 Z 的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过 kill 发送 SIGTERM 或者 SIGKILL 信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被 init 进程接管,init 进程会 wait() 这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程就能瞑目而去了。

    测试代码

    孤儿进程测试程序如下所示:

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    
    int main()
    {
        pid_t pid;
        //创建一个进程
        pid = fork();
        //创建失败
        if (pid < 0)
        {
            perror("fork error:");
            exit(1);
        }
        //子进程
        if (pid == 0)
        {
            printf("I am the child process.\n");
            //输出进程ID和父进程ID
            printf("pid: %d\tppid:%d\n",getpid(),getppid());
            printf("I will sleep five seconds.\n");
            //睡眠5s,保证父进程先退出
            sleep(5);
            printf("pid: %d\tppid:%d\n",getpid(),getppid());
            printf("child process is exited.\n");
        }
        //父进程
        else
        {
            printf("I am father process.\n");
            //父进程睡眠1s,保证子进程输出进程id
            sleep(1);
            printf("father process is  exited.");
        }
        return 0;
    }

     

    僵尸进程测试程序如下所示:

    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <stdlib.h>
    
    int main()
    {
        pid_t pid;
        pid = fork();
        if (pid < 0)
        {
            perror("fork error:");
            exit(1);
        }
        else if (pid == 0)
        {
            printf("I am child process.I am exiting.\n");
            exit(0);
        }
        printf("I am father process.I will sleep two seconds\n");
        //等待子进程先退出
        sleep(2);
        //输出进程信息
        system("ps -o pid,ppid,state,tty,command");
        printf("father process is exiting.\n");
        return 0;
    }

    测试结果如下所示:

     

    僵尸进程解决办法

    • 通过信号机制
      • 子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程
    • fork两次
      • 将子进程成为孤儿进程,从而其的父进程变为 init 进程,通过 init 进程可以处理僵尸进程

    参考资料:

    10. 守护进程

    Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

    守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。

    一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。

    守护进程的名称通常以d结尾,比如sshd、xinetd、crond等

    编写守护进程的一般步骤步骤:

    1. 在父进程中执行 fork 并 exit 推出;

    2. 在子进程中调用 setsid 函数创建新的会话;

    3. 在子进程中调用 chdir 函数,让根目录 / 成为子进程的工作目录;

    4. 在子进程中调用umask函数,设置进程的umask 为 0;

    5. 在子进程中关闭任何不需要的文件描述符

    参考资料:

    11. 上下文切换

    上下文切换,有时也称做进程切换或任务切换,是指CPU从一个进程或线程切换到另一个进程或线程。 在操作系统中,CPU 切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务

     

    三、死锁

    资源分类:(1)可重用资源;(2)消耗资源

    1. 什么是死锁

    造成死锁的原因就是多个线程或进程对同一个资源的争抢或相互依赖。一个最简单的解释就是你去面试,面试官问你告诉我什么是死锁,我就录用你,你回答面试官你录用我,我告诉你。

    如果一个进程集合里面的每个进程都在等待只能由这个集合中的其他一个进程(包括他自身)才能引发的事件,这种情况就是死锁。

    这个定义可能有点拗口,下面用一个简单例子说明。

    资源 A、B,进程 C、D 描述如下:

    资源 A 和资源 B,都是不可剥夺资源,现在进程 C 已经申请了资源 A,进程 D 也申请了资源 B,进程 C 接下来的操作需要用到资源 B,而进程 D 恰好也在申请资源A,进程 C、D 都得不到接下来的资源,那么就引发了死锁。

    然后套用回去定义:如果一个进程集合里面(进程 C 和进程 D)的每个进程(进程 C 和进程 D)都在等待只能由这个集合中的其他一个进程(对于进程 C,他在等进程 D;对于进程 D,他在等进程 C)才能引发的事件(释放相应资源)。

    这里的资源包括了软的资源(代码块)和硬的资源(例如扫描仪)。 资源一般可以分两种:可剥夺资源(Preemptable)不可剥夺资源 (Nonpreemptable)。一般来说对于由可剥夺资源引起的死锁可以由系统的重新分配资源来解决,所以一般来说大家说的死锁都是由于不可剥夺资源所引起的。

    2. 死锁的必要条件

    • 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
    • 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
    • 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
    • 循环等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。

    3. 死锁的处理方法

    1. 处理死锁的策略

    • 鸵鸟策略

      • 把头埋在沙子里,假装根本没发生问题。
      • 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。
      • 大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。
    • 检测死锁并且恢复。

    • 仔细地对资源进行动态分配,以避免死锁。

    • 通过破除死锁四个必要条件之一,来防止死锁产生。

    2. 死锁检测与死锁恢复

    不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。

    (一)每种类型一个资源的死锁检测

    上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。

    图 a 可以抽取出环,如图 b,它满足了环路等待条件,因此会发生死锁。

    每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。

    (二)每种类型多个资源的死锁检测

    上图中,有三个进程四个资源,每个数据代表的含义如下:

    • E 向量:资源总量
    • A 向量:资源剩余量
    • C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
    • R 矩阵:每个进程请求的资源数量

    进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P2 可以执行,执行后释放 P2 拥有的资源,A = (4 2 2 1) 。P1 也可以执行。所有进程都可以顺利执行,没有死锁。

    算法总结如下:

    每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。

    1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。
    2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
    3. 如果没有这样一个进程,算法终止。

    (三)死锁恢复

    • 利用抢占恢复
    • 利用回滚恢复
    • 通过杀死进程恢复

    3. 死锁预防

    在程序运行之前预防发生死锁,确保系统永远不会进入死锁状态。

    (一)破坏互斥条件

    例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。(把互斥地封装成可以同时访问的,例如:打印机的缓存)

    (二)破坏占有和等待条件

    一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。

    但是,这种策略也有如下缺点:

    • 在许多情况下,一个进程在执行之前不可能知道它所需要的全部资源。这是由于进程在执行时是动态的,不可预测的;
    • 资源利用率低。无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间却一直占有它们,造成长期占着不用的状况。这显然是一种极大的资源浪费;
    • 降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数就必然少了。

    (三)破坏不可抢占条件

    允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。

    (四)破坏循环等待

    实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。这种策略与前面的策略相比,资源的利用率和系统吞吐量都有很大提高,但是也存在以下缺点:

    • 限制了进程对资源的请求,同时给系统中所有资源合理编号也是件困难事,并增加了系统开销;
    • 为了遵循按编号申请的次序,暂不使用的资源也需要提前申请,从而增加了进程对资源的占用时间。

    4. 死锁避免

    在程序运行时避免发生死锁,在使用前进行判断,只允许不会出现死锁的进程请求资源。

    (一)安全状态

    图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数,Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b),运行结束后释放 B,此时 Free 变为 5(图 c);接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态时安全的。

    定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。

    安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。

    (二)单个资源的银行家算法

    一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。

     

    上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。

    (三)多个资源的银行家算法

     

    上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。

    检查一个状态是否安全的算法如下:

    • 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。
    • 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
    • 重复以上两步,直到所有进程都标记为终止,则状态时安全的。

    如果一个状态不是安全的,需要拒绝进入这个状态。

    4. 如何在写程序的时候就避免死锁

    网易有道面经

    所谓的死锁呢,发生的主要原因在于了有多个进程去竞争资源,也就是同时去抢占。

    可以自己写一个支持多线程的消息管理类,单开一个线程访问独占资源,其它线程用消息交互实现间接访问。 这种机制适应性强、效率高,更适合多核环境。

    四、内存管理

    1. 虚拟内存

    虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。

    为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到一部分不在物理内存中的地址空间时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。

    从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序称为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。

    2. 分页系统地址映射

    • 内存管理单元(MMU):管理着地址空间和物理内存的转换。
    • 页表(Page table):页(地址空间)和页框(物理内存空间)的映射表。例如下图中,页表的第 0 个表项为 010,表示第 0 个页映射到第 2 个页框。页表项的最后一位用来标记页是否在内存中。

    下图的页表存放着 16 个页,这 16 个页需要用 4 个比特位来进行索引定位。因此对于虚拟地址(0010 000000000100),前 4 位是用来存储页面号,而后 12 位存储在页中的偏移量。

    (0010 000000000100)根据前 4 位得到页号为 2,读取表项内容为(110 1),它的前 3 为为页框号,最后 1 位表示该页在内存中。最后映射得到物理内存地址为(110 000000000100)。

    3. 页面置换算法

    在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。

    页面置换算法和缓存淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。

    页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。

    1. 最佳

    Optimal

    所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。

    是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。

    举例:一个系统为某进程分配了三个物理块,并有如下页面引用序列:

    开始运行时,先将 7, 0, 1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。

    2. 最近最久未使用

    LRU, Least Recently Used

    虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。

    为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面时最近最久未访问的。

    因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。

    3. 最近未使用

    NRU, Not Recently Used

    每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:

    • R=0,M=0
    • R=0,M=1
    • R=1,M=0
    • R=1,M=1

    当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。

    NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。

    4. 先进先出

    FIFO, First In First Out

    选择换出的页面是最先进入的页面。

    该算法会将那些经常被访问的页面也被换出,从而使缺页率升高。

    5. 第二次机会算法

    FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:

    当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。

    6. 时钟

    Clock

    第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面链接起来,再使用一个指针指向最老的页面。

    4. 分段

    虚拟内存采用的是分页技术,也就是将地址空间划分成固定大小的页,每一页再与内存进行映射。

    下图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。

    分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。

    5. 段页式

    程序的地址空间划分成多个拥有独立地址空间的段,每个段上的地址空间划分成大小相同的页。这样既拥有分段系统的共享和保护,又拥有分页系统的虚拟内存功能。

    6. 分页与分段的比较

    • 对程序员的透明性:分页透明,但是分段需要程序员显示划分每个段。
    • 地址空间的维度:分页是一维地址空间,分段是二维的。
    • 大小是否可以改变:页的大小不可变,段的大小可以动态改变。
    • 出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。

    五、设备管理

    1. 磁盘结构

    • 盘面(Platter):一个磁盘有多个盘面;
    • 磁道(Track):盘面上的圆形带状区域,一个盘面可以有多个磁道;
    • 扇区(Track Sector):磁道上的一个弧段,一个磁道可以有多个扇区,它是最小的物理储存单位,目前主要有 512 bytes 与 4 K 两种大小;
    • 磁头(Head):与盘面非常接近,能够将盘面上的磁场转换为电信号(读),或者将电信号转换为盘面的磁场(写);
    • 制动手臂(Actuator arm):用于在磁道之间移动磁头;
    • 主轴(Spindle):使整个盘面转动。

    2. 磁盘调度算法

    读写一个磁盘块的时间的影响因素有:

    • 旋转时间(主轴旋转磁盘,使得磁头移动到适当的扇区上)
    • 寻道时间(制动手臂移动,使得磁头移动到适当的磁道上)
    • 实际的数据传输时间

    其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。

    1. 先来先服务

    FCFS, First Come First Served

    • 按照磁盘请求的顺序进行调度

    • 公平对待所有进程

    • 在有很多进程的情况下,接近随机调度的性能

    • 优点是公平和简单。缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。

    2. 最短寻道时间优先

    SSTF, Shortest Seek Time First

    优先调度与当前磁头所在磁道距离最近的磁道。

    虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两边的磁道请求更容易出现饥饿现象。

    3. 电梯算法

    SCAN

    电梯总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。

    电梯算法(扫描算法)和电梯的运行过程类似,总是按一个方向来进行磁盘调度,直到该方向上没有未完成的磁盘请求,然后改变方向。

    因为考虑了移动方向,因此所有的磁盘请求都会被满足,解决了 SSTF 的饥饿问题。

    六、链接

    1. 编译系统

    以下是一个 hello.c 程序:

    #include <stdio.h>
    int main()
    {
        printf("hello, world\n");
        return 0;
    }

    在 Unix 系统上,由编译器把源文件转换为目标文件。

    gcc -o hello hello.c
    

    这个过程大致如下:

    1. 预处理阶段 (Preprocessing phase)


    预处理(cpp)根据以字符 # 开头的命令,修改原始的 C 程序,生成扩展名为 .i 的文件。

    $ gcc -E hello.c -o hello.i

    2. 编译阶段 (Compilation phase)


    编译器(cc1)将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。

    $ gcc -S hello.i -o hello.s

    3. 汇编阶段 (Assembly phase)

    编译器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标文件 hello.o 中。

    $ as hello.s -o hello.o

    4. 链接阶段 (Linking phase)

    printf 函数是标准 C 库中的一个函数,在 printf.o 这个单独预编译好的目标文件中。连接器(ld)将 printf.o 和 hello.o 合并,结果得到 hello 可执行目标文件

    $ gcc hello.o -o hello

    2. 静态链接

    静态连接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:

    • 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
    • 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。

    3. 目标文件

    • 可执行目标文件:可以直接在内存中执行;
    • 可重定向目标文件:可与其它可重定向目标文件在链接阶段合并,创建一个可执行目标文件;
    • 共享目标文件:这是一种特殊的可重定向目标文件,可以在运行时被动态加载进内存并链接;

    4. 动态链接

    静态库有以下两个问题:

    • 当静态库更新时那么整个程序都要重新进行链接;
    • 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。

    共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具有以下特点:

    • 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
    • 在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。

    展开全文
    qq_34959478 2018-09-08 16:03:13
  • 、基础知识: 1、JVM、JRE和JDK的区别: JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性。 java语言是跨平台,jvm不是跨平台的。 JRE(Java Runtime Environment):java的运行环境,包括...

    一、基础知识:
    1、JVM、JRE和JDK的区别:
    JVM(Java Virtual Machine):java虚拟机,用于保证java的跨平台的特性。
    java语言是跨平台,jvm不是跨平台的。
    JRE(Java Runtime Environment):java的运行环境,包括jvm+java的核心类库。
    JDK(Java Development Kit):java的开发工具,包括jre+开发工具
    2、环境变量path和classpath的作用是什么?
    (1)path是配置Windows可执行文件的搜索路径,即扩展名为.exe的程序文件所在的目录,
    用于指定DOS窗口命令的路径。
    (2)Classpath是配置class文件所在的目录,用于指定类搜索路径,JVM就是通过它来寻找该类的class类文件的。
    3、变量有什么用?为什么要定义变量?什么时候用?
    答:变量的作用:用来存储数据。
    为什么要定义变量:用来不断的存放同一类型的常量,并可以重复使用
    4、&和&&的区别?
    答:(1)&&会出现短路,如果可以通过第一个表达式判断出整个表达式的结果,则不继续后面表达式的运算;
    只能操作boolean类型数据;
    (2)&不会出现短路,将整个表达式都运算。既可以操作boolean数据还可以操作数。
    5、标示符命名规则:
    由数字(0-9),大小写英文字母,以及_和$组成。
    不能以数字开头。
    不能使用关键字来自定义命名。
    6、数据类型:
    (1)基本数据类型(4类8种):
    整数类型:byte、short、int、long
    浮点数类型:float、double
    字符类型:char
    布尔类型:boolean(ture false)
    (2)引用数据类型:

    接口
    数组
    7、类型转换
    精度从高到低 double float long int short(char) byte
    (1)自动类型转换 将一个低精度—高精度
    (2)强制类型转换 将一个高精度—低精度(精度会下降)
    8、java语言的三种技术架构
    J2EE:企业版
    是为开发企业环境下的应用程序提供的一套解决方案。
    该技术体系中包含的技术如 Servlet、Jsp等,主要针对于Web应用程序开发。
    J2SE:标准版
    是为开发普通桌面和商务应用程序提供的解决方案。
    该技术体系是其他两者的基础,可以完成一些桌面应用程序的开发。
    比如Java版的扫雷。
    J2ME:小型版
    是为开发电子消费产品和嵌入式设备提供的解决方案。
    该技术体系主要应用于小型电子消费类产品,如手机中的应用程序等。
    9、java的跨平台性:
    通过Java语言编写的应用程序在不同的系统平台上都可以运行。
    跨平台的原因:
    只要在需要运行java应用程序的操作系统上,先安装一个Java虚拟机(JVM Java Virtual Machine)即可。
    由JVM来负责Java程序在该系统中的运行。
    10、有符号数据的表示法(次重点)
    原码,反码(原码取反),补码(反码+1)。
    11、函数
    定义:函数就是定义在类中的具有特定功能的一段独立小程序。
    特点:
    定义函数可以将功能代码进行封装
    便于对该功能进行复用
    函数只有被调用才会被执行
    函数的出现提高了代码的复用性
    对于函数没有具体返回值的情况,返回值类型用关键字void表示,
    那么该函数中的return语句如果在最后一行可以省略不写。
    函数的应用两个明确:
    明确要定义的功能最后的结果是什么?
    明确在定义该功能的过程中,是否需要未知内容参与运算
    12、重载:
    概念:在同一个类中,允许存在一个以上的同名函数,只要它们的参数个数或者参数类型不同即可。
    特点:与返回值类型无关,只看参数列表(参数类型以及参数个数)。
    好处:方便于阅读,优化了程序设计。
    13、数组:
    概念:同一种数据类型的集合。
    好处:可以自动给数组中的元素从0开始编号,方便操作这些元素。
    14、内存结构:
    栈内存:用于存储局部变量,当数据使用完,所占空间会自动释放。
    堆内存:数组和对象,通过new建立的实例都存放在堆内存中。
    方法区:静态成员、构造函数、常量池、线程池
    本地方法区:window系统占用
    寄存器:

    二、面向对象
    1、面向对象思想:
    (1)概述:面向对象是相对于面向过程而言的,面向过程强调的是功能,面向对象强调的是将功能封装进对象,
    强调具备功能的对象;
    (2)思想特点:
    A:是符合人们思考习惯的一种思想;
    B:将复杂的事情简单化了;
    C:将程序员从执行者变成了指挥者;

    比如我要达到某种结果,我就寻找能帮我达到该结果的功能的对象,如我要洗衣服我就买洗衣机,
    至于怎么洗我不管。
    (3)特征:
    封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式
    继承: 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义
    这些属性和行为,只要继承那个类即可。
    多态: 一个对象在程序不同运行时刻代表的多种状态,父类或者接口的引用指向子类对象

    2、类和对象:
    类:对现实世界中某类事物的描述,是抽象的,概念上的定义。
    对象:事物具体存在的个体。
    3:成员变量和局部变量的区别(重点)
    (1)作用域
    成员变量:针对整个类有效。
    局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
    (2)存储位置
    成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
    局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。
    当方法调用完,或者语句结束后,就自动释放。
    (3)初始值
    成员变量:有默认初始值。
    局部变量:没有默认初始值,使用前必须赋值。
    4、匿名对象
    (1)匿名对象就是没有名字的对象。是对象的一种简写形式。
    (2)应用场景
    A:只调用一次类中的方法。
    B:可以作为实际参数在方法传递中使用
    5、封装:
    指隐藏对象的属性和实现细节,仅对外提供公共访问方式;比如电脑机箱、笔记本等
    好处:
    将变化隔离;
    方便使用;
    提高复用性;
    提高安全性
    6、关键字private:封装在代码中的体现
    (1)私有的意思,权限修饰符
    (2)用来修饰成员变量和成员函数
    (3)用private修饰的成员只在本类中有效
    (4)私有是封装的一种体现
    7、构造方法:
    (1)特点:
    方法名与类名相同
    没有返回类型
    没有返回值
    (2)作用:构造函数是用于创建对象,并对其进行初始化赋值,对象一建立就自动调用相对应的构造函数,
    (3)构造方法的注意事项:
    A:如果一个自定义类没有构造方法,系统会默认给出一个无参构造方法。
    B:如果一个自定义类提供了构造方法,那么,系统将不再给出无参构造方法。
    这个时候,你可以不使用无参构造方法。
    如果你想使用,那么,就必须手动给出无参构造方法。

    建议:一般情况下,我们的自定义类都要手动给出无参构造方法。
    (4)构造方法和成员方法的区别
    A:格式区别
    构造方法和类名相同,并且没有返回类型,也没有返回值。
    普通成员方法可以任意起名,必须有返回类型,可以没有返回值。
    B:作用区别
    构造方法用于创建对象,并进行初始化值。
    普通成员方法是用于完成特定功能的。
    C:调用区别
    构造方法是在创建对象时被调用的,一个对象建立,只调用一次相应构造函数
    普通成员方法是由创建好的对象调用,可以调用多次

    8、构造代码块:
    (1)作用:给对象进行初始化,对象一建立就执行,而且优先于构造函数执行
    (2)构造代码块和构造函数的区别:
    构造代码块是给所有不同对象的共性进行统一初始化
    构造函数是给对应的对象进行初始化
    9、this关键字
    (1)this关键字代表本类对象的一个引用,谁调用this所在的方法,this就代表谁
    (2)this的使用场景
    A:用于区分同名成员变量和局部变量;
    B:在定义函数时,该函数内部要用到调用该函数的对象时,因为此时对象还没建立,故this代表此对象
    B:构造函数间调用
    **这个时候,this(参数)必须作为第一条语句存在。
    10、Person p = new Person();在内存中做了哪些事情。
    (1)将Person.class文件加载进内存中。
    (2)如果p定义在主方法中,那么,就会在栈空间开辟一个变量空间p。
    (3)在堆内存给对象分配空间。
    (4)对对象中的成员进行默认初始化。
    (5)对对象中的成员进行显示初始化。
    (6)调用构造代码块对对象进行初始化。(如果没有就不执行)
    (7)调用构造方法对对象进行初始化。对象初始化完毕。
    (8)将对象的内存地址赋值给p变量,让p变量指向该对象。
    11、static关键字:
    (1)静态的意思,用来修饰成员变量和成员函数
    (2)静态的特点:
    随着类的加载而加载
    优先于对象存在
    对所有对象共享
    可以被类名直接调用
    (3)静态的注意事项
    A:静态方法只能访问静态成员
    为什么:因为静态的内容是随着类的加载而加载,它是先进内存的。
    B:静态方法中不能使用this,super关键字
    C:主方法是静态的
    public static void main(String[] args)
    public:公共的意思,是最大权限修饰符。
    static:由于jvm调用main方法的时候,没有创建对象。
    只能通过类名调用。所以,main必须用static修饰。
    void:由于main方法是被jvm调用,不需要返回值。用void修饰。
    main:main是主要的意思,所以jvm采用了这个名字。是程序的入口。

    String[]:字符串数组
    args:数组名

    在运行的时候,通过java命令给args数组赋值。
    格式:java MainTest hello world itcast
    (4)静态变量和成员变量的区别
    A:调用方式
    静态变量也称为类变量,可以直接通过类名调用。也可以通过对象名调用。
    这个变量属于类。
    成员变量也称为实例变量,只能通过对象名调用。这个变量属于对象。
    B:存储位置
    静态变量存储在方法区长中的静态区。
    成员变量存储在堆内存。
    C:生命周期
    静态变量随着类的加载而存在,随着类的消失而消失。生命周期长。
    成员变量随着对象的创建而存在,随着对象的消失而消失。
    D:与对象的相关性
    静态变量是所有对象共享的数据。
    成员变量是每个对象所特有的数据。
    (5)静态的优点和弊端
    优点:
    对对象的共享数据进行单独空间的存储,节省内存,没有必要每个对象都存储一份
    可直接被类名调用
    弊端:
    生命周期过长,随着类的消失而消失
    访问出现权限,即静态虽好但只能访问静态
    (6)什么使用使用静态呢?
    A:当所有对象共享某个数据的时候,就把这个成员变量定义为静态修饰的。
    B:当某个方法没有访问该类中的非静态成员,就可以把这个方法定义为静态修饰。

    静态的生命周期比较长,所以一般不推荐使用。
    (7)静态代码块
    A:它只执行一次,它比main还先执行。
    B:执行顺序
    静态代码块–构造代码块–构造方法

    12、制作API(次重点)
    API(全拼):Application Program Interface 应用程序编程接口。
    (1)类中的内容需要用文档注释。
    (2)使用JDK\bin目录下的javadoc工具。
    格式:javadoc -d 目录 -author -version ArrayTool.java
    13、单例设计模式:
    (1)设计模式:
    解决某类问题行之有效的方法,是一种思想,是规律的总结
    (2)用来保证某个类在内存中只有一个对象
    (3)保证唯一性的思想及步骤
    **为了避免其他程序建立该类对象,先禁止其他程序建立该类对象,即将构造函数私有化
    **为了其他程序访问到该类对象,须在本类中创建一个该类私有对象
    **为了方便其他程序访问到该类对象,可对外提供一个公共访问方式

    比如API中的Runtime类就是单例设计模式。

    (4)单例设计模式的两种方式
    A:饿汉式 当类加载的时候,就创建对象。

            class Student
            {
                private Student(){}
    
                private static final Student s = new Student();
    
                public static Student getInstance()
                {
                    return s;
                }
            }

    B:懒汉式 当使用的使用,才去创建对象。

            class Student
            {
                private Student(){}
    
                private static final Student s = null;
    
                public static Student getInstance()
                {
                    if(s==null) 
                    {
                        //线程1就进来了,线程2就进来了。
                        s = new Student();
                    }
                    return s;
                }
            }

    饿汉式和懒汉式**
    是类一加载进内存就创建好了对象;
    懒汉式则是类才加载进内存的时候,对象还没有存在,只有调用了getInstance()方法时,
    对象才开始创建。
    **
    懒汉式是延迟加载,如果多个线程同时操作懒汉式时就有可能出现线程安全问题,解决线程安全问题
    可以加同步来解决。但是加了同步之后,每一次都要比较锁,效率就变慢了,
    所以可以加双重判断来提高程序效率。
    注:开发常用饿汉式,因为饿汉式简单安全。懒汉式多线程的时候容易发生问题Math类的使用(重点)
    (1)数学操作类:该类没有构造函数,方法均为静态的
    (2)掌握内容
    A:成员变量
    **E:比任何其他值都更接近e(即自然对数的底数)的double值。
    **PI:比任何其他值都更接近pi(即圆的周长与直径之比)的double值。
    B:成员方法
    **static double abs(double a)
    返回 double 值的绝对值。返回绝对值
    **static double ceil(double a)
    返回最小的(最接近负无穷大)double 值,该值大于等于参数,并等于某个整数。
    **static double floor(double a)
    返回最大的(最接近正无穷大)double 值,该值小于等于参数,并等于某个整数。
    **max:返回两个值中较大的那个
    **min:返回两个值中较小的那个
    **static long round(double a) 返回最接近参数的 long。
    static int round(float a) 返回最接近参数的 int。
    **static double random()
    返回带正号的 double 值,该值大于等于 0.0 且小于 1.0。
    **static double pow(double a, double b)
    返回第一个参数的第二个参数次幂的值。
    **static double sqrt(double a)
    返回正确舍入的 double 值的正平方根。
    15、Random类的使用(重点)
    (1)产生随机数的类
    (2)掌握内容
    A:构造方法
    **Random() 创建一个新的随机数生成器。
    **Random(long seed) 使用单个 long 种子创建一个新的随机数生成器。
    B:成员方法
    **int nextInt() 返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。
    **int nextInt(int n) 返回一个伪随机数,它是取自此随机数生成器序列的、
    在 0(包括)和指定值(不包括)之间均匀分布的 int 值。
    16、Scanner类的使用
    (1)可以获取从键盘的输入数据
    (2)掌握内容
    构造方法:
    Scanner(InputStream source) 构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
    如:Scanner sc = new Scanner(System.in);
    方法摘要
    sc.nextInt();获取整型数据
    sc.nextLine();获取字符串数据
    17、继承(重点)
    (1)把很多类的相同特征和行为进行抽取,用一个类来描述。让多个类和这个类产生一个关系。
    这样的话,多个类就可以省略很多代码。这个关系就是继承。java中用extends关键字表示。
    (2)继承的体系结构
    A:多个具体的对象,不断的向上抽取共享的内容,最终形成了一个体系。这个体系叫做继承体系。
    B:继承体系的学习和使用原则
    **学习顶层的内容。因为他是整个体系的共性内容。
    **创建子类使用。也就是使用底层的具体对象。
    (3)继承的特点:
    A:java中只能单继承,没有多继承。
    B:java可以有多重(层)继承。
    (4)继承的好处:
    继承的出现提高了代码的复用性。
    继承的出现让类与类之间产生了关系,提供了多态的前提。
    (5)子父类中的成员关系
    A:成员变量
    在子类方法中使用一个变量时:
    首先,在方法的局部变量中找这个变量,有则使用。
    否则,在本类中找成员变量,有则使用。
    否则,在父类中找成员变量,有则使用。
    否则,报错。
    B:成员方法
    用子类对象使用一个方法时。
    首先,在子类中找这个方法,有则使用。
    否则,在父类中找这个方法,有则使用。
    否则,报错。

    重写和重载的区别?
    重载:在同一类中。方法名相同,参数列表不同。重载可以改变返回类型。
    重写:在不同类中(子父类中)。
    方法声明相同(返回类型,方法名,参数列表均相同)。
    重写需要注意:
    **子类方法的访问权限要大于等于父类方法的访问权限。
    **静态只能重写静态。但是这种情况一般不会出现。

    构造方法
    **子类的实例化过程
    *子类创建对象时,会先去创建父类的对象。
    默认是去调用父类的无参构造方法。
    *子类构造方法中,第一行默认是super()
    *为什么子类中第一行会默认有super()
    因为他继承父类的成员使用,使用前这些成员必须初始化,
    而他们是父类的成员,所以,必须通过父类进行初始化。
    所以,会先创建一个父类的对象。
    **当父类没有无参构造方法时
    必须使用this或者super调用其他的构造方法。
    (6)this和super的区别
    this:代表本类对象的引用。
    super:代表父类的存储空间。

    18、final关键字(重点)
    (1)最终的意思,可以用于修饰类,方法,变量。
    (2)final修饰的类不能被继承。
    final修饰的方法不能被重写。
    final修饰的变量是一个常量。只能被赋值一次。
    内部类只能访问被final修饰的局部变量。

    19、抽象类(重点)
    (1)多个类有相同的方法声明,但是方法体不一样。这个时候,我们考虑把方法声明进行抽取。
    让子类继承后,自己去实现方法体。没有方法体的方法,我们需要用抽象标志下。
    抽象的关键字是:abstract。
    (2)抽象类:
    该方法称为抽象方法,包含抽象方法的类就是抽象类。
    (3)抽象类的特点:
    A:抽象类和抽象方法都要用abstract进行修饰
    B:抽象类不能被实例化
    C:抽象类中不一定有抽象方法,但是,有抽象方法的类一定是抽象类。
    (4)抽象类中数据的特点
    A:成员变量
    抽象类中可以有变量,也可以有常量。
    B:成员方法
    抽象类中可以有抽象方法,也可以有非抽象方法。
    C:构造方法
    抽象类是一个类,所以,它有构造方法。
    虽然本身不能实例化。但是可以给子类实例化使用。
    (5)抽象类中的问题
    A:抽象类中是否有构造方法?能不能被实例化?如果不能,为什么有构造方法?
    抽象类有构造方法。
    抽象类不能被实例化。
    抽象类中的构造方法供子类实例化调用。
    B:抽象关键字abstract不可以和哪些关键字共存?
    **private:
    私有内容子类继承不到,所以,不能重写。
    但是abstract修饰的方法,要求被重写。两者冲突。
    **final
    final修饰的方法不能被重写。
    而abstract修饰的方法,要求被重写。两者冲突。
    **static
    假如一个抽象方法能通过static修饰,那么这个方法,就可以直接通过类名调用。
    而抽象方法是没有方法体的,这样的调用无意义。所以,不能用static修饰。
    C:抽象类中可不可以没有抽象方法?如果可以,这样的类有什么用吗?
    抽象类可以没有抽象方法。

    20、接口interface
    (1)当一个类中的方法都是抽象的时候,java提供了另一种表示方式,叫接口。
    用interface关键字表示。类与接口关系用implements表示。
    (2)接口的成员特点
    A:成员变量
    是常量,默认修饰 public static final
    B:成员方法
    都是抽象的,默认修饰 public abstract
    (3)关系
    A:类与类的关系
    是继承关系。类与类只能单继承,可以多重继承。
    B:类和接口的关系
    是实现关系。类可以多实现接口。
    类在继承一个类的同时,可以实现多个接口。
    C:接口和接口的关系
    是继承关系。接口可以多继承接口。
    (4)接口的特点
    A:是对外暴露的规则
    B:是功能的扩展
    C:接口的出现降低耦合性。
    耦合(类与类之间的关系)
    内聚(类完成功能的能力)
    编程规范:低耦合,高内聚。
    D:接口可以多实现。如:CPU和主板、笔记本的USB插口、插座
    (5)接口和抽象类的区别
    A:抽象类只能被单继承
    接口可以多实现,接口的出现避免了多继承的局限性。
    B:抽象类中的数据特点:
    成员变量:可以是变量,也可以是常量
    成员方法:可以是抽象方法,也可以是非抽象方法
    构造方法:有构造方法
    接口中的数据特点:
    成员变量:是常量。默认修饰 public static final
    成员方法:都是抽象方法。都有默认修饰 public abstract
    构造方法:没有构造方法
    C:抽象类中定义的是继承体系中的共性功能。
    接口中定义的是继承体系中的扩展功能。
    D:抽象类被继承是”is a”关系:xx是yy的一种
    接口被实现是”like a”关系:xx像yy的一种

    21、多态:
    (1)同一个对象,在程序不同时刻的多种运行状态。举例:动物,狗是狗,狗是动物。水(气态,液态,固态)
    (2)多态前提
    A:存在着继承或者实现关系
    B:有方法的重写
    C:父类(接口)引用指向子类(实现)对象
    (3)多态的好处和弊端:
    好处:多态的存在提高了程序的扩展性和后期可维护性
    弊端:虽然可以预先使用,但是只能访问父类中已有的功能,运行的是后期子类的功能内容。
    不能预先使用子类中定义的特有功能。
    (4)多态中对象调用成员的特点
    Fu f = new Zi();
    A:成员变量
    编译看左边,运行看左边
    B:成员方法
    编译看左边,运行看右边
    C:静态方法
    编译看左边,运行看左边
    (5)多态的思想
    指挥同一批对象做事情。举例:带兵打仗,下课等。

    22、instanceof关键字
    A:用于判断某个对象是否是某种类型。
    B:格式
    对象名 instanceof 子类(实现)名

    23、Object类:
    (1)是所有类的根类,超类。
    java中提供的类以及我们自定义的类都直接或者间接的继承自Object类。
    (2)Object类中的方法
    A:void finalize()
    当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
    B:Class getClass()
    获取对象的字节码文件的描述类,后面再讲反射的时候还会在说这个类。
    String name = s.getClass().getName();
    C:int hashCode()
    获取对象的哈希值。其实就是对象的内存地址值十进制表示
    D:String toString()
    返回对象的字符串表示。
    表示格式:
    getClass().getName()+”@”+Integer.toHexString(hashCode());

    一般我们输出对象名的时候,其实底层调用的就是该对象的toString()方法。
    

    这种返回没有意义,所以,我们会重写这个方法,显示类的成员变量信息。
    E:boolean equals(Object obj)
    用于比较两个对象的地址值是否相同。
    我们获取对象后,比较它的地址值意义不大。所以也会对这个方法进行重写。
    重写要完成什么功能,是根据需求定的。
    (3)==和equals的用法:
    A:==怎么用?
    **可以用于比较基本数据类型,比较的就是基本数据类型的值是否相等。
    **可以用于比较引用数据类型,比较的是对象的地址值是否相等。
    B:equals怎么用?
    equals只能用于比较引用数据类型的。
    **Object提供的equals是用于比较对象地址值是否相同。
    **自定义类中,如果重写了equals方法,那么就是按照你自己的需求来比较的。

    24、package关键字
    (1)包:其实就是文件夹。用于区分不同包下相同的类名。
    (2)好处:
    A:对类文件进行分类管理。
    B:给类提供了多层命名空间
    aaa.Demo
    bbb.Demo
    C:写在程序文件的第一行。
    D:包也是一种封装形式。

    25、import关键字
    (1)导入包的关键字
    (2)格式:
    import 包名;
    (3)注意:
    A:一个程序文件中只有一个package,可以有多个import。
    B:用来导包中的类,不导入包中的包。
    C:通常写import mypack.Demo,明确自己使用的类。
    (4)关键字的顺序
    类,包,导包这些关键的顺序。
    包 – > 到包 – > 类

    26、不同修饰符可以修饰哪些内容
    常用修饰符修饰内容的作用域

    常用修饰符的修饰内容

    一般格式:
        成员变量:
        权限修饰符+static/final+数据类型+成员变量名
        public static final int NUM = 10;
    
        成员方法:
        权限修饰符+static/final/abstract+返回类型+方法名
    

    27、内部类(次重点)
    (1)把一个类定义在某个类中的,这个类就被称为内部类,内置类,嵌套类。
    (2)访问特点:
    A:内部类可以直接访问外部类中的成员,因为内部类持有外部类的引用,
    格式为:外部类名.this
    B:外部类要想访问内部类的成员,必须创建对象访问。
    (3)内部类的访问格式:
    A:当内部类定义在外部类的成员位置,而且非私有,则可以在其他外部类中直接建立内部类对象
    格式:外部类名.内部类名 变量名 = new 外部类对象.内部类对象
    如:Outer.Inner in = new Outer().new Inner()
    B:当内部类在外部类成员位置,且被static修饰时
    **外部其他类可直接访问静态内部类的非静态成员
    格式:new 外部类名.内部类名().内部类成员
    如:new Outer.Inner().function();
    **外部其他类可直接访问静态内部类的静态成员
    格式:new 外部类名.内部类名.内部类成员
    如:new Outer.Inner.function();
    (4)什么使用时候内部类呢?
    假如有A类和B类,A类想直接访问B类的成员,B类访问A类成员的时候,
    需要创建A类对象进行访问,这个时候,就可以把A类定义为B类的内部类。
    (5)内部类的位置
    A:成员位置
    **可以被private修饰(Body,Heart)
    **可以被static修饰。(它访问的外部类的成员必须是静态的)
    B:局部位置
    **可以直接访问外部类中的成员,因为还持有外部类的持用
    也可以直接访问局部成员,但是局部成员要用final修饰。
    注意:局部内部类不能用private和static修饰
    (6)通过class文件我们就可以区分是否带有内部类,以及内部类的位置
    Outer Inner:Outer 1Inner:局部内部类

    28、匿名内部类(局部内部类的简写) (重点)
    (1)前提:继承一个类或者实现一个接口
    (注意不要弄混匿名内部类的前提和多态的前提)
    (2)格式:
    new 父类名或者接口名()
    {
    重写父类方法或者实现接口中的方法。
    也可以自定义其他方法。
    };
    (3)什么时候定义匿名内部类?
    匿名内部类只是为了简化书写,匿名内部类有局限,通常定义匿名内部类时,该类方法不超过3个
    (4)匿名内部类的好处和弊端:
    好处:简化代码书写
    弊端:
    不能直接调用自己的特有方法
    不能执行强转换动作
    如果该类里面方法较多,不允许使用匿名内部类

    29、模板设计模式:
    在定义功能时,功能的一部分是确定的,有一部分是不确定的,而且确定的部分在使用不确定的部分,
    可将不确定的部分暴露出去,由该类的子类去完成。
    如:求一段程序的运行时间例子。

    30、异常
    (1)程序运行过程中的不正常现象就叫异常。
    (2)导致程序运行不正常的现象有很多,所以,就有很多的异常对象。
    而这些异常对象存在着共性的内容,所以,可以不断的进行抽取。最终形成了异常的体系结构。
    异常体系的根类是:Throwable
    Throwable:
    |–Error:重大的问题,我们处理不了。也不需要编写代码处理。比如说内存溢出。
    |–Exception:一般性的错误,是需要我们编写代码进行处理的。
    |–RuntimeException:运行时异常,这个我们也不需要处理。
    其实就是为了让他在运行时出问题,然后我们回来修改代码。
    (3)异常的分类
    异常有两种:
    编译时被检测异常:
    该异常在编译时,如果没有处理(没有抛也没有try),编译失败。
    该异常被标识,代表这可以被处理。
    运行时异常(编译时不检测)
    在编译时,不需要处理,编译器不检查。
    该异常的发生,建议不处理,让程序停止。需要对代码进行修正。
    (4)异常体系的特点:
    异常体系中的所有类及其子类对象都具备可抛性。也就是说可以被throw和throws关键字所操作。
    (5)main方法是如何处理异常的。
    A:在main里面编写代码进行处理
    B:交给jvm自己进行处理。采用的是jvm的默认处理方式。
    其实就是相当于调用了异常对象的printStackTrace()方法。
    (6)Throwable类的学习
    getMessage():获取异常信息,返回字符串。
    toString():获取异常类名和异常信息,返回字符串。
    printStackTrace():获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
    (7)异常的处理·
    A:try…catch…finally
    基本格式:
    try
    {
    可能出现异常的代码
    }
    catch(异常对象)
    {
    异常处理代码
    }
    finally
    {
    释放资源
    }

        变形格式:
            try...catch
            try...catch...catch...
            try...catch...catch...finally
        **多个异常同时被捕获的时候,记住一个原则:
            先逮小的,再逮大的。
        **finally:永远被执行,除非退出jvm。System.exit(0);
            面试题2个。
            ***:final,finally,finalize区别。
               final是最终的意思。它可以用于修饰类,成员变量,成员方法。
               它修饰的类不能被继承,它修饰的变量时常量,它修饰的方法不能被重写。
    
               finally:是异常处理里面的关键字。
               它其中的代码永远被执行。特殊情况:在执行它之前jvm退出。System.exit(0);
    
               finalize:是Object类中的一个方法。
               它是于垃圾回收器调用的方式。
    
            ***:假如catch中有return语句, finally里中的代码会执行吗?
               是在return前,还是在return后呢?
               会,在return前执行finally里面的代码。
    (8)Exception和RuntimeException的区别
        A:Exception:一般性的错误,是需要我们编写代码进行处理的。  
        B:RuntimeException:运行时异常,这个我们也不需要处理。
                       其实就是为了让他在运行时出问题,然后我们回来修改代码。
            在用throws抛出一个的时候,如果这个异常是属于RuntimeException的体系的时候,
            我们在调用的地方可以不用处理。(RuntimeException和RuntimeException的子类)
    
            在用throws抛出一个的时候,如果这个异常是属于Exception的体系的时候,
            我们在调用的地方必须进行处理或者继续抛出。
    (9)自定义异常
        定义类继承Exception或者RuntimeException
        1,为了让该自定义类具备可抛性。
        2,让该类具备操作异常的共性方法。
    
            class MyExcepiton extends Exception
            {
                MyExcepiton(){}
    
                MyExcepiton(String message)
                {
                    super(message);
                }
            }
    
            class MyException extends RuntimeException
            {
                MyExcepiton(){}
    
                MyExcepiton(String message)
                {
                    super(message);
                }
            }
    (10)throws和throw的区别
        A:有throws的时候可以没有throw。
           有throw的时候,如果throw抛的异常是Exception体系,那么必须有throws在方法上声明。
        B:throws用于方法的声明上,其后跟的是异常类名,后面可以跟多个异常类,之间用逗号隔开
           throw用于方法体中,其后跟的是一个异常对象名
    

    三、多线程:
    1、进程和线程:
    进程:正在进行的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
    线程:进程内部的一条执行路径或者一个控制单元。
    两者的区别:
    一个进程至少有一个线程
    进程在执行过程中拥有独立的内存单元,而多个线程共享内存;
    2、jvm多线程的启动是多线程吗?
    java的虚拟机jvm启动的是单线程,就有发生内存泄露的可能,而我们使用java程序没出现这样的问题,
    也就是jvm启动至少有两个线程,一个执行java程序,一个执行垃圾回收。所以是多线程。
    2、多线程的优势:
    解决了多部分同时运行的问题,提高效率
    3、线程的弊端:
    线程太多会导致效率的降低,因为线程的执行依靠的是CPU的来回切换。
    4、什么叫多线程:
    一个进程中有多个线程,称为多线程。
    5、实现多线程的方法:
    实现多线程可以通过继承Thread类和实现Runnable接口。
    (1)继承Thread
    定义一个类继承Thread类
    复写Thread类中的public void run()方法,将线程的任务代码封装到run方法中
    直接创建Thread的子类对象,创建线程
    调用start()方法,开启线程(调用线程的任务run方法)
    //另外可以通过Thread的getName()获取线程的名称。

    (2)实现Runnable接口;
        定义一个类,实现Runnable接口;
        覆盖接口的public void run()的方法,将线程的任务代码封装到run方法中;
        创建Runnable接口的子类对象
        将Runnabl接口的子类对象作为参数传递给Thread类的构造函数,创建Thread类对象
                       (原因:线程的任务都封装在Runnable接口子类对象的run方法中。
                 所以要在线程对象创建时就必须明确要运行的任务)。
        调用start()方法,启动线程。
    
    两种方法区别:
        (1)实现Runnable接口避免了单继承的局限性
        (2)继承Thread类线程代码存放在Thread子类的run方法中
           实现Runnable接口线程代码存放在接口的子类的run方法中;
           在定义线程时,建议使用实现Runnable接口,因为几乎所有多线程都可以使用这种方式实现
    

    6、创建线程是为什么要复写run方法?
    Thread类用于描述线程。Thread类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
    7、start()和run方法有什么区别?
    调用start方法方可启动线程,而run方法只是thread的一个普通方法,调用run方法不能实现多线程;
    Start()方法:
    start方法用来启动线程,实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的
    代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,
    一旦得到cpu时间片(执行权),就开始执行run()方法,这里方法run()称为线程体,
    它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
    Run()方法:
    run()方法只是Thread类的一个普通方法,如果直接调用Run方法,程序中依然只有主线程这一个线程,
    其程序执行路径还是只有一条,还是要等待run方法体执行完毕后才可继续执行下面的代码,
    这样就没有达到多线程的目的。
    8、线程的几种状态:
    新建:new一个Thread对象或者其子类对象就是创建一个线程,当一个线程对象被创建,但是没有开启,这个时候,
    只是对象线程对象开辟了内存空间和初始化数据。
    就绪:新建的对象调用start方法,就开启了线程,线程就到了就绪状态。
    在这个状态的线程对象,具有执行资格,没有执行权。
    运行:当线程对象获取到了CPU的资源。
    在这个状态的线程对象,既有执行资格,也有执行权。
    冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。
    当然,他们可以回到运行状态。只不过,不是直接回到。
    而是先回到就绪状态。
    死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成了垃圾。
    9、sleep()和wait()的区别:
    (1)这两个方法来自不同的类,sleep()来自Thread类,和wait()来自Object类。
    (2)sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,
    要让b线程睡觉要在b的代码中调用sleep。而wait()是Object类的非静态方法
    (3)sleep()释放资源不释放锁,而wait()释放资源释放锁;
    (4)使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
    10、多线程安全问题:
    (1)原因:当程序的多条语句在操作线程共享数据时(如买票例子中的票就是共享资源),由于线程的随机性导致
    一个线程对多条语句,执行了一部分还没执行完,另一个线程抢夺到cpu执行权参与进来执行,
    此时就导致共享数据发生错误。比如买票例子中打印重票和错票的情况。
    (2)解决方法:对多条操作共享数据的语句进行同步,一个线程在执行过程中其他线程不可以参与进来
    11、Java中多线程同步是什么?
    同步是用来解决多线程的安全问题的,在多线程中,同步能控制对共享数据的访问。如果没有同步,当一个线程在
    修改一个共享数据时,而另外一个线程正在使用或者更新同一个共享数据,这样容易导致程序出现错误的结果。
    12、什么是锁?锁的作用是什么?
    锁就是对象
    锁的作用是保证线程同步,解决线程安全问题。
    持有锁的线程可以在同步中执行,没有锁的线程即使获得cpu执行权,也进不去。
    13、同步的前提:
    (1)必须保证有两个以上线程
    (2)必须是多个线程使用同一个锁,即多条语句在操作线程共享数据
    (3)必须保证同步中只有一个线程在运行
    14、同步的好处和弊端
    好处:同步解决了多线程的安全问题
    弊端:多线程都需要判断锁,比较消耗资源
    15、同步的两种表现形式:
    (1)同步代码块:
    可以指定需要获取哪个对象的同步锁,使用synchronized的代码块同样需要锁,但他的锁可以是任意对象
    考虑到安全问题,一般还是使用同一个对象,相对来说效率较高。

        注意:
        **虽然同步代码快的锁可以使任何对象,但是在进行多线程通信使用同步代码快时,
          必须保证同步代码快的锁的对象和,否则会报错。
        **同步函数的锁是this,也要保证同步函数的锁的对象和调用wait、notify和notifyAll的对象是
          同一个对象,也就是都是this锁代表的对象。
        格式:
        synchronized(对象)
        {
            需同步的代码;
        }
    (2)同步函数
        同步方法是指进入该方法时需要获取this对象的同步锁,在方法上使用synchronized关键字,
        使用this对象作为锁,也就是使用了当前对象,因为锁住了方法,所以相对于代码块来说效率相对较低。
        注:静态同步函数的锁是该方法所在的类的字节码文件对象,即类名.class文件
        格式:
        修饰词 synchronized 返回值类型 函数名(参数列表)
        {
            需同步的代码;
        }
    
    在jdk1.5后,用lock锁取代了synchronized,个人理解也就是对同步代码块做了修改,
    并没有提供对同步方法的修改,主要还是效率问题吧。
    

    16、多线程的单例设计模式:保证某个类中内存中只有一个对象
    (1)饿汉式:

            class Single
            {
                private Single(){}//将构造函数私有化,不让别的类建立该类对象
                private static final Single s=new Single();//自己建立一个对象
                public static Single getInstance()//提供一个公共访问方式
                {
                    return s;
                }
            }
    (2)懒汉式:
    
            class Single
            {
                private Single(){} 
                private static Single s;
                public static Single getInstance()
                {
                    if(s==null)
                        s=new Single();
                    return s;
                }
            }
    饿汉式和懒汉式的区别:
        **
        饿汉式是类一加载进内存就创建好了对象;
        懒汉式则是类加载进内存的时候,对象还没有存在,只有调用了getInstance()方法时,对象才开始创建。   
        **
        懒汉式是延迟加载,如果多个线程同时操作懒汉式时就有可能出现线程安全问题,解决线程安全问题
        可以加同步来解决。但是加了同步之后,每一次都要比较锁,效率就变慢了,
        所以可以加双重判断来提高程序效率。
        如将上述懒汉式的Instance函数改成同步:
    
            public static Single getInstance()
            {
                if(s==null)
                {
                    synchronized(Single.class)
                    {
                        if(s==null) 
                            s=new Single();
                    }
                }
                return s;
            }

    17、死锁
    两个线程对两个同步对象具有循环依赖时,就会发生死锁。即同步嵌套同步,而锁却不同。
    18、wait()、sleep()、notify()、notifyAll()
    wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
    sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
    notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,
    而是由JVM确定唤醒哪个线程(一般是最先开始等待的线程),而且不是按优先级。
    Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
    18、为什么wait()、notify()、notifyAll()这些用来操作线程的方法定义在Object类中?
    (1)这些方法只存在于同步中;
    (2)使用这些方法时必须要指定所属的锁,即被哪个锁调用这些方法;
    (3)而锁可以是任意对象,所以任意对象调用的方法就定义在Object中。
    19、多线程间通讯:
    多线程间通讯就是多个线程在操作同一资源,但是操作的动作不同.
    (1)为什么要通信
    多线程并发执行的时候, 如果需要指定线程等待或者唤醒指定线程, 那么就需要通信.比如生产者消费者的问题,
    生产一个消费一个,生产的时候需要负责消费的进程等待,生产一个后完成后需要唤醒负责消费的线程,
    同时让自己处于等待,消费的时候负责消费的线程被唤醒,消费完生产的产品后又将等待的生产线程唤醒,
    然后使自己线程处于等待。这样来回通信,以达到生产一个消费一个的目的。
    (2)怎么通信
    在同步代码块中, 使用锁对象的wait()方法可以让当前线程等待, 直到有其他线程唤醒为止.
    使用锁对象的notify()方法可以唤醒一个等待的线程,或者notifyAll唤醒所有等待的线程.
    多线程间通信用sleep很难实现,睡眠时间很难把握。

    20、Lock和Condition
    实现提供比synchronized方法和语句可获得的更广泛的锁的操作,可支持多个相关的Condition对象
    Lock是个接口
    锁是控制多个线程对共享数据进行访问的工具。

    JDK1.5中提供了多线程升级的解决方案:
    将同步synchonized替换成了显示的Lock操作,将Object中的wait、notify、notifyAll替换成了Condition对象。
    该对象可以Lock锁进行获取
    
    Lock的方法摘要:
        void lock()  获取锁。 
        Condition newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。 
        void unlock() 释放锁。
    Condition方法摘要:
        void await() 造成当前线程在接到信号或被中断之前一直处于等待状态。
        void signal() 唤醒一个等待线程。          
        void signalAll() 唤醒所有等待线程。
    

    21、停止线程:
    stop方法已经过时,如何停止线程?
    停止线程的方法只有一种,就是run方法结束。如何让run方法结束呢?
    开启多线程运行,运行代码通常是循环体,只要控制住循环,就可以让run方法结束,也就是结束线程。

        特殊情况:当线程属于冻结状态,就不会读取循环控制标记,则线程就不会结束。
        为解决该特殊情况,可引入Thread类中的Interrupt方法结束线程的冻结状态;
        当没有指定的方式让冻结线程恢复到运行状态时,需要对冻结进行清除,强制让线程恢复到运行状态
    

    22、interrupt:
    void interrupt() 中断线程:
    中断状态将被清除,它还将收到一个 InterruptedException
    22、守护线程(后台线程)
    setDaemon(boolean on):将该线程标记为守护线程或者用户线程。
    当主线程结束,守护线程自动结束,比如圣斗士星矢里面的守护雅典娜,
    在多线程里面主线程就是雅典娜,守护线程就是圣斗士,主线程结束了,
    守护线程则自动结束。
    当正在运行的线程都是守护线程时,java虚拟机jvm退出;所以该方法必须在启动线程前调用;

    守护线程的特点:
        守护线程开启后和前台线程共同抢夺cpu的执行权,开启、运行两者都没区别,
        但结束时有区别,当所有前台线程都结束后,守护线程会自动结束。      
    

    23、多线程join方法:
    void join() 等待该线程终止。
    void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。
    throws InterruptedException
    特点:当A线程执行到B线程的join方法时,A就会等待B线程都执行完,A才会执行
    作用: join可以用来临时加入线程执行;
    24、多线程优先级:yield()方法
    yield():暂停当前正在执行的线程对象,并执行其他线程
    setPriority(int newPriority):更改线程优先级
    int getPriority() 返回线程的优先级。
    String toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组

    (1)MAX_PRIORITY:最高优先级(10级)
    (1)Min_PRIORITY:最低优先级(1级)
    (1)Morm_PRIORITY:默认优先级(5级)
    

    25、什么是ThreadLocal类,怎么使用它?
    ThreadLocal类提供了线程局部 (thread-local) 变量。是一个线程级别的局部变量,并非“本地线程”。
    ThreadLocal 为每个使用该变量的线程,提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本

    下面是线程局部变量(ThreadLocal variables)的关键点:
        一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。
        ThreadLocal 实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。 
        当多个线程访问 ThreadLocal 实例时,每个线程维护 ThreadLocal 提供的独立的变量副本。
        常用的使用可在 DAO 模式中见到,当 DAO 类作为一个单例类时,
        数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)
    

    26、什么时候抛出InvalidMonitorStateException异常?为什么?
    调用 wait ()/notify ()/notifyAll ()中的任何一个方法时,如果当前线程没有获得该对象的锁,
    那么就会抛出 IllegalMonitorStateException 的异常
    也就是说程序在没有执行对象的任何同步块或者同步方法时,
    仍然尝试调用 wait ()/notify ()/notifyAll ()时。由于该异常是 RuntimeExcpetion 的子类,
    所以该异常不一定要捕获(尽管你可以捕获只要你愿意
    作为 RuntimeException,此类异常不会在 wait (),notify (),notifyAll ()的方法签名提及。
    27、在静态方法上使用同步时会发生什么事?
    同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,
    线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。
    它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。
    28、当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?
    可以,一个非同步方法总是可以被调用而不会有任何问题。
    实际上,Java 没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。
    如果一个方法没有声明为同步,即使你在使用共享数据Java照样会调用,而不会做检查是否安全,
    所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问(critial section access),
    如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。
    29、在一个对象上两个线程可以调用两个不同的同步实例方法吗?
    不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。
    所以只有执行完该方法释放对象锁后才能执行其它同步方法。
    30、什么是线程饿死,什么是活锁?
    线程饿死和活锁虽然不像死锁一样是常见的问题,但是对于并发编程的设计者来说就像一次邂逅一样。
    当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。
    JavaAPI 中线程活锁可能发生在以下情形:
    当所有线程在程序中执行 Object.wait (0),参数为 0 的 wait 方法。
    程序将发生活锁直到在相应的对象上有线程调用 Object.notify ()或者 Object.notifyAll ()。
    当所有线程卡在无限循环中。

    四、集合框架
    1:String类:字符串(重点)
    (1)多个字符组成的一个序列,叫字符串。
    生活中很多数据的描述都采用的是字符串的。而且我们还会对其进行操作。
    所以,java就提供了这样的一个类供我们使用。
    (2)创建字符串对象
    A:String():无参构造
    **举例:
    String s = new String();
    s = “hello”;
    sop(s);
    B:String(byte[] bys):传一个字节数组作为参数 *
    **举例
    byte[] bys = {97,98,99,100,101};
    String s = new String(bys);
    sop(s);
    C:String(byte[] bys,int index,int length):把字节数组的一部分转换成一个字符串 *
    **举例
    byte[] bys = {97,98,99,100,101};
    String s = new String(bys,1,2);
    sop(s);
    D:String(char[] chs):传一个字符数组作为参数 *
    **举例
    char[] chs = {‘a’,’b’,’c’,’d’,’e’};
    String s = new String(chs);
    sop(s);
    E:String(char[] chs,int index,int length):把字符数组的一部分转换成一个字符串 *
    **举例
    char[] chs = {‘a’,’b’,’c’,’d’,’e’};
    String s = new String(chs,1,2);
    sop(s);
    F:String(String str):把一个字符串传递过来作为参数
    char[] chs = {‘a’,’b’,’c’,’d’,’e’};
    String ss = new String(s);
    sop(ss);
    G:直接把字符串常量赋值给字符串引用对象(最常用) *
    **举例
    String s = “hello”;
    sop(s);
    (3)面试题
    A:请问String s = new String(“hello”);创建了几个对象。
    两个。一个”hello”字符串对象,在方法区的常量池;一个s对象,在栈内存。

        B:请写出下面的结果
            String s1 = new String("abc");
            Strign s2 = new String("abc");
            String s3 = "abc";
            String s4 = "abc";
    
            sop(s1==s2);  //false
            sop(s1==s3);  //false
            sop(s3==s4);  //true
        C:字符串对象一旦被创建就不能被改变。
            指的是字符串常量值不改变。
    (4)字符串中各种功能的方法
        A:判断
        ****    boolean equals(Object anObject):判断两个字符串的内容是否相同,复写了Object的方法
        ****    boolean equalsIgnoreCase(String anotherString):判断两个字符串的内容是否相同,
                                    不区分大小写
        ****    boolean contains(String s):判断一个字符串中是否包含另一个字符串
                        注意:判断字符串是否包含特殊字符.直接表示为str.contains(".")
            boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
            boolean startsWith(String suffix):测试此字符串是否以指定的前缀开始
            boolean isEmpty():测试字符串是否为空
        B:获取
        *****   int length():返回此字符串的长度
        *****   char charAt(int index):返回指定索引处的 char值
        *****   int indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引。 
            int indexOf(int ch, int fromIndex):返回在此字符串中第一次出现指定字符处的索引,
                               从指定的索引开始搜索。 
            int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引。 
            int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次
                                出现处的索引,从指定的索引开始。 
        *** int lastIndexOf(int ch):返回指定字符在此字符串中最后一次出现处的索引。 
            int lastIndexOf(int ch, int fromIndex) 
                返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。 
            int lastIndexOf(String str) 
                返回指定子字符串在此字符串中最右边出现处的索引。 
            int lastIndexOf(String str, int fromIndex) 
                返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。 
        *****   String substring(int beginIndex) (注意:该方法substring的String是小写!!!)
                返回一个新的字符串,它是此字符串的一个子字符串。 
            String substring(int beginIndex, int endIndex) (注意该方法的String是小写!!!)
                返回一个新字符串,它是此字符串的一个子字符串,包含头不包含尾。 
        C:转换
        *****   byte[] getBytes():(很常用!)从字符串到字节数组的方法
            void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 
                将字符从此字符串复制到目标字符数组。 
        *****   char[] toCharArray():(很常用!)从字符串到字符数组的方法
        ****    static String copyValueOf(char[] data) 
                返回指定数组中表示该字符序列的 String。 
            static String copyValueOf(char[] data, int offset, int count) 
                返回指定数组中表示该字符序列的 String。 
        *****   static String valueOf(数据类型):把该数据类型的数据转换成字符串。
        *** String toLowerCase():把字符串转换成小写
            String toUpperCase():把字符串转换成大写
        *** 字符串的连接
            String concat(String str):将指定字符串连接到此字符串的结尾。
        D:替换
            String replace(char oldChar, char newChar):用新字符替换旧字符(替换所有)
            String replace(String target, String replacement):用新的子串换旧串
        E:分割
            String[] split(String regex):根据指定的字符串把一个字符串分割成一个字符串数组
        F:  
            String trim():去除字符串的前后空格
        G:  
            int compareTo(String anotherString) 
                按字典顺序比较两个字符串。 
            int compareToIgnoreCase(String str) 
                按字典顺序比较两个字符串,不考虑大小写。 
    (5)练习
        1:模拟登录,给三次机会,并提示还有几次.
        默认的用户名和密码为admin。 区分大小写。
        自己从键盘输入用户名和密码。
    
        2:给定一个字符串统计,统计大写字母,小写字母,数字出现的个数.
        ***注意:不包括特殊字符
        从键盘输入一个不包含特殊字符的字符串(只有26个字母和0-9组成)。
    
        3:给定一个字符串,把它变成首字母大写,其他字母小写的字符串.
        从键盘输入一个字符串,全部26个字母组成的。
    
        4:子串在整串中出现的次数。
        也就是说:获取一个字符串中,指定的字串在该字符串中出现的次数.
        例如:
        "nbasdnbafllgnbahjnbakqqqqlnba"  在这个字符串中,多有个nba.
    
        5:对字符串中字符进行自然顺序排序。
        "basckd"-->"abcdks"
    
        先留做思考内容:
        6:两个字符串的最大相同子串。
        两个字符串的最大相同子串。
        比如:
        "sadabcdfghjkl"
        werabcdtyu"
    

    2:StringBuffer
    (1)字符串的缓冲区,是一个容器。
    (2)它和String的区别
    它是缓冲区可变长度的。
    (3)构造方法
    StringBuffer() 构造一个其中不带字符的字符串缓冲区,初始容量为 16 个字符。
    StringBuffer(int num) 构造一个不带字符,但具有指定初始容量的字符串缓冲区。
    StringBuffer(String str) 构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。
    (4)常用方法
    A:增加数据
    **append :添加各种类型的数据
    **insert : 在容器指定位置插入各种类型的数据。
    B:删除数据
    **deleteCharAt : 删除指定位置的字符
    **delete 还可以用于清空StringBuffer的缓冲区
    C:替换
    **replace
    D:获取
    **charAt
    E:长度和容量
    **length() 元素的个数
    **capacity 元素的理论值
    F:获取元素的位置
    **indexOf
    **lastIndexOf
    G:截取
    **substring(int start)
    **substring(int start,int end)
    H:反转
    **reverse
    (5)字符串和StringBuffer的转换
    String–>StringBuffer通过构造:
    如:StringBuffer sb = new StringBuffer(String str)
    StringBuffer–String通过toString方法
    如:StringBuffer sb = new StringBuffer();
    sb.toString();

    3:StringBuilder
    和StringBuffer的功能是一样的,但是有区别:
    StringBuffer(JDK1.0)是线程安全的。
    StringBuilder(JDK1.5)不保证线程安全。

    一般来说,我们写的程序都是单线程的,所以,用StringBuilder,效率高。
    
    JDK版本的升级原则:
    A:提高效率
    B:提高安全性
    C:简化书写
    

    4:基本数据类型的对象包装类
    (1)为了更方便的操作每个基本数据类型,java对其提供了很多的属性和方法供我们使用。
    (2)用途:
    **将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能操作该数据。
    **常用的操作之一:用于基本数据类型与字符串之间的转换。
    A:方便操作
    B:用于和字符串进行相互转换
    (3)基本数据类型和对象类型的对应
    byte Byte
    short Short
    int Integer
    long Long
    float Float
    double Double
    boolean Boolean
    char Character
    (4)构造方法

        字段摘要:
            static int MAX_VALUE 值为 2^31-1 的常量,它表示 int 类型能够表示的最大值         
            static int MIN_VALUE  值为 -2^31 的常量,它表示 int 类型能够表示的最小值
            static Class<Integer> TYPE 表示基本类型int的Class 实例
    
        Integer(int value) 构造一个新分配的Integer对象,它表示指定的int值。
        Inreger(String s) 注意:s必须是纯数字的字符串。否则会有异常NumberFormatException
    
    (5)几个常用的方法
        Integer.toBinaryString();
            以二进制(基数 2)无符号整数形式返回一个整数参数的字符串表示形式。
        Integer.toOctalString();
            以八进制(基数 8)无符号整数形式返回一个整数参数的字符串表示形式。
        Integer.toHexString();
            以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式。
        static int Integer.parseInt(String s) 将字符串参数作为有符号的十进制整数进行解析,
            字符串必须是int型范围内的数字字符串
        static int Integer.parseInt(String s,int basic) 
            使用第二个参数指定的基数,将字符串参数解析为有符号的整数.
            字符串必须是int型范围内的数字字符串
        short shortValue() 以short类型返回该Integer的值。          
        int intValue() 以int类型返回该Integer的值。  
        static Integer valueOf(int num) 返回一个表示指定的 int 值的 Integer 实例。
        static Integer valueOf(String s) 返回保存指定的String的值的Integer对象。           
                static Integer valueOf(String s, int radix) 
            返回一个Integer对象,该对象中保存了用第二个参数提供的基数进行
            解析时从指定的String中提取的值。 
    
    (6)类型转换
        int -- Integer
            int num = 20;
            A:Integer i = new Integer(num);
            B:Integer i = Integer.valueOf(num);
        Integer -- int
            Integer i = new Integer(20);
            A:int num = i.intValue();
    
        int -- String
            int num = 20;
            A:String s = String.valueOf(num);
            B:String s = ""+num;
            C:String s = Integer.toString(num);
        String -- int
            String s = "20";
            A:int num = Integer.parseInt(s);
            B:Integer i = new Integer(s);或者Integer i = Integer.valueOf(s);
              int num = i.intValue();   
    

    6、集合框架:
    (1)为什么出现集合类?
    面向对象对事物的体现都是以对象的形式,为了方便对多个对象的操作,就对对象进行存储。
    集合就是存储对象最常用的一种方式.
    (2)数组和集合都是容器,两者有何不同?
    **数组长度固定,而集合长度是可变的
    **数组值可以存储对象,还可以存储基本数据类型;而集合只能存储对象
    **数组存储数据类型是固定的,而集合存储的数据类型不固定
    (3)集合类的特点:
    集合只能存储对象
    集合的长度是可变的
    集合可以存储不同类型的对象
    (4)集合类框架(重要!!!要分清几种容器间的区别):
    **Collection:顶层接口
    |—>List:列表,元素是有序的(元素带角标索引),可以有重复元素,可以有null元素。
    |—>ArrayList(JDK1.2):底层的数据结构是数组数据结构,特点是查询速度快(因为带角标),
    但是增删速度稍慢,因为当元素多时,增删一个元素则所有元素的角标都得改变
    线程不同步。默认长度是10,当超过长度时,按50%延长集合长度。
    |—>LinkedList(JDK1.2):底层数据结构式链表数据结构(即后面一个元素记录前一个),
    特点:查询速度慢,因为每个元素只知道前面一个元素,但增删速度快
    因为元素再多,增删一个,只要让其前后的元素重新相连即可
    线程是不同步的。
    |—>Vector(JDK1.0):底层数据结构是数组数据结构.特点是查询和增删速度都很慢。
    默认长度是10,当超过长度时,按100%延长集合长度。
    线程同步。
    (Vector功能跟ArrayList功能一模一样,已被ArrayList替代)

           **List使用注意!
            |--->ArrayList:
            (1)当往ArrayList里面存入元素没什么要求时,即只要求有序就行时;
    
            (2)当往ArrayList里面存入元素要求不重复时,比如存入学生对象,当同名同姓时
               视为同一个人,则不往里面存储。则定义学生对象时,需复写equals方法
    
               public boolean equals(Object obj)
                   {
                    if(!(obj instanceof Student))
                        return false;
                    Student stu = (Student)obj;
                    return this.name.equals(stu.name)&&this.age==stu.age;
                   }
               则往ArrayList集合通过add存入学生对象时,集合底层自己会调用学生类的equals方法,
               判断重复学生则不存入。
             注:对于List集合,无论是add、contains、还是remove方法,判断元素是否相同,
                 都是通过复写equals方法来判断!
    
            |--->LinkedList
            (1)LinkLedist的特有方法:
                 boolean offerFirst(E e)  在此列表的开头插入指定的元素。
                 boolean offerLast(E e) 在此列表末尾插入指定的元素。
                 E peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
                 E peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。
                 E pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null。
                 E pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。
            (2)通过LinkLedist的特有方法,可以实现某些数据特殊方式的存取,比如堆栈和队列。
    
                一般情况下,使用哪种List接口下的实现类呢?
                如果要求增删快,考虑使用LinkedList
                如果要求查询快,考虑使用ArrayList
                如果要求线程安全,考虑使用Vector。
    
    
    
             |--->Set:集合,元素是无序的(因为没有索引),元素不可以重复。可以有null元素。
                |--->HashSet(JDK1.2):底层数据结构是哈希表、存取速度快、元素唯一、线程不同步。
                     保证性元素唯一的原理:
                     先判断元素的hashCode值是否相同,再判断两元素的equals方法是否为true
                     (往HashSet里面存的自定义元素要复写hashCode和equals方法,
                     以保证元素的唯一性!)
                |--->TreeSet:底层数据结构式二叉树。可以对Set集合中的元素进行排序。元素有序、线程不同步。
                     保证元素唯一性的依据:compareTo方法return 0
                     TreeSet排序的第一种方式:让元素自身具备比较性,比如八种基本数据类型或则字符串,
                                 实现Compareble接口,覆盖compareTo方法,
                                 此方式是元素的自然顺序             
                     TreeSet排序的第一种方式:当元素自身不具备比较性(比如存储学生对象时)或者具备的
                                 比较性不是我们所需要的比较性时(比如想字符串的长度排序),
                                 此时就需要让集合自身具备自定义的比较性。 
                                 那如何让集合自身具备比较性呢?可在集合初始化时,
                                 就让集合具备比较方式。即定义一个类,
                                 实现Comparator接口,覆盖compare方法。
    
            **Set集合使用注意事项:
            (1)HashSet:
                  通过new的方式往HashSet里面存的元素的hashCode都不同,但通常我们定义对象,
                  比如学生对象时,虽然是new的两个学生对象,但是当他们name和age一样时,我们认为是
                  同一个对象,所以为了保证元素的唯一性,我们通常在往HashSet集合里面存储元素时,
                  在定义对象的类中通常复写hashCode和equals方法。
    
                  public int hashCode()
                      {
                    return name.hashCode()+age*39;
                      }
                      public boolean equals(Object obj)
                      {
                    if(!(obj instanceof Student))
                        return false;
                    Student stu = (Student)obj;
                    return this.name.equals(stu.name)&&this.age==stu.age;
                      }
                 HashSet是如何保证元素唯一性的呢?
                  **如果两元素的hashCode值不同,则不会调用equals方法
                  **如果两元素的hashCode值相同,则继续判断equals是否返回true;
                  **hashCode和equals方法虽然定义在自定义对象类里面,但不是我们手动调用
                    而是往HashSet集合里面存储元素的时候,集合底层自己调用hashCode和equals
                它自己拿对象去判断,自己判断两元素是否是同一个元素。
    
            (2)TreeSet:
                 TreeSet要求往里面存的元素具备比较性,否则会报错。
                 TreeSet排序的第一种方式:让元素自身具备比较性
                  定义对象类,实现Compareble接口,复写compareTo方法,此方式是元素的自然顺序
    
                      class Student implements Comparable
                      {
                        private String name;
                        private int age;
                        public Student(String name,int age)
                        {
                            this.name=name;
                            this.age=age;
                        }
                        public String getName()
                        {
                            return name;
                        }
                        public int getAge()
                        {
                            return age;
                        }
                        public int compareTo(Object obj)
                        {
                            if(!(obj instanceof Student))
                                throw new RuntimeException("不是学生对象!");
                            Student stu = (Student)obj;
                            int num = this.age-stu.age;
                            if(num==0)
                                return this.name.compareTo(stu.name);
                            return num;
                        }
                      }
                TreeSet排序的第一种方式:让集合具备比较性
                     当元素自身不具备比较性(比如存储学生对象时)或者具备的
                     比较性不是我们所需要的比较性时(比如想字符串的长度排序),
                     此时就需要让集合自身具备自定义的比较性。 
                     那如何让集合自身具备比较性呢?可在集合初始化时,
                     就让集合具备比较方式。即定义一个类,
                     实现Comparator接口,覆盖compare方法。
    
                     class StringLengthComparator implements Comparator
                     {
                        public int compare(Object obj1,Object obj2)
                        {
                            String s1 = (String)obj1;
                            String s2 = (String)obj2;
                            int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
                            if(num==0)
                                return s1.compareTo(s2);
                            return num;
                        }
                     }
                     class TreeSetTest
                     {
                        public static void main(String[] args)
                        {
                            TreeSet ts = new TreeSet(new StringLengthComparator());
                            ts.add("addfg");
                            ts.add("dfg");
                            ts.add("agtuug");
                            ts.add("vgjkg");
                            sop(ts);
                        }
                     }
                 基本数据类型或字符串对象均实现了Comparable接口,故同种类型基本数据间具备比较性,即自然顺序。
    
    
    **Map:顶层接口,该集合存储的是键值对,而且键是唯一的,Map和Set很像,Set集合底层就是使用了Map集合。
        Map集合没有迭代器,要取出元素必须先将Map集合转换成Set集合才能遍历元素
       |--->HashTable(JDK1.0): 
        底层是哈希表数据结构;
        不可以使用null键和null值;
        用作键的对象必须实现hashCode和equals方法来保证键的唯一性
        线程同步,效率低
       |--->HashMap(JDK1.2):
        底层是哈希表数据结构;
        允许使用null键和null值;
        线程不同步,效率高;
        保证元素唯一性的:
             原理:先判断元素的hashCode值是否相同,再判断两元素的equals方法是否为true
             (往HashSet里面存的自定义元素要复写hashCode和equals方法,
             以保证元素的唯一性!)
    
            class Student {
                private String name;
                private int age;
                public Student(String name, int age) {
                    super();
                    this.name = name;
                    this.age = age;
                }
                public int getAge() {
                    return age;
                }
                public void setAge(int age) {
                    this.age = age;
                }
                public String getName() {
                    return name;
                }
                public void setName(String name) {
                    this.name = name;
                }
    
                @Override
                public int hashCode(){
                    return name.hashCode()+age*34;
                }
                @Override
                public boolean equals(Object obj){
    
                    if(!(obj instanceof Student))
                        return false;
                    Student stu = (Student)obj;
                    return this.name.equals(stu.name)&&this.age==stu.age;
                }
            public class HashMapDemo1 {
                public static void main(String[] args) {
                    Map<Student , String> hmap = new HashMap<Student , String>();
                    hmap.put(new Student("001",20), "beijing");
                    hmap.put(new Student("002",25), "hebei");
                    hmap.put(new Student("003",50), "hainan");
                    hmap.put(new Student("001",20), "beijing");
    
                    System.out.println(hmap.size());
                    Set<Student> keySet = hmap.keySet();
                    Iterator<Student> it = keySet.iterator();
                    while(it.hasNext()){
                        Student stu = it.next();
                        String addr = hmap.get(stu);
                        System.out.println(stu.getName()+".."+stu.getAge()+"::"+addr);
                    }   
                }   
            }   
       |--->TreeMap(JDK1.0):
        底层是二叉树结构;
        允许使用null键和null值;
        线程不同步;
        可以给Map集合中的键进行排序.
        TreeMap排序的第一种方式:让元素自身具备比较性,比如八种基本数据类型或则字符串,
                 实现Compareble接口,覆盖compareTo方法,
                 此方式是元素的自然顺序             
        TreeMap排序的第一种方式:当元素自身不具备比较性(比如存储学生对象时)或者具备的
                 比较性不是我们所需要的比较性时(比如想字符串的长度排序),
                 此时就需要让集合自身具备自定义的比较性。 
                 那如何让集合自身具备比较性呢?可在集合初始化时,
                 就让集合具备比较方式。即定义一个类,
                 实现Comparator接口,覆盖compare方法。
    
            class Student implements Comparable<Student>{
                private String name;
                private int age;
                public Student(String name, int age) {
                    super();
                    this.name = name;
                    this.age = age;
                }
                public int getAge() {
                    return age;
                }
                public void setAge(int age) {
                    this.age = age;
                }
                public String getName() {
                    return name;
                }
                public void setName(String name) {
                    this.name = name;
                }
                @Override
                public int compareTo(Student stu) {
                    int num = new   Integer(this.age).compareTo(new Integer(stu.age));
                    if(num==0)
                        return this.name.compareTo(stu.name);
                    return num;
                }           
            }
    
            public class HashMapDemo1 {
                public static void main(String[] args) {
    
                    Map<Student , String> tmap = new TreeMap<Student , String>();
                    tmap.put(new Student("001",20), "beijing");
                    tmap.put(new Student("002",25), "hebei");
                    tmap.put(new Student("003",50), "hainan");
                    tmap.put(new Student("001",20), "beijing");
    
                    System.out.println(tmap.size());
                    Set<Student> keySet1 = tmap.keySet();
                    Iterator<Student> it1 = keySet1.iterator();
                    while(it1.hasNext()){
                        Student stu = it1.next();
                        String addr = tmap.get(stu);
                        System.out.println(stu.getName()+".."+stu.getAge()+"::"+addr);      
                    }
                }
            }
    **Iterator:对collection进行迭代的迭代器.迭代器取代了Enumeration。
        迭代器和枚举的区别:
        迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的collection移除元素
        方法名称得到了改进,简化书写 
    **LisIterator:系列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表        
    **Comparable:此接口强行对实现它的每个类的对象进行整体自然排序。使元素具备比较性
    **Comparator:强行对某个对象collection进行整体排序的比较函数,使集合具备比较性
    **Collections:此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。
    **Arrays:此类包含用来操作数组(比如排序和搜索)的各种静态方法
    

    7、集合类各容器方法:
    **接口Collection方法摘要(没有构造方法)
    a)添加:
    i. boolean add(E e)
    j. boolean addAll(Collection c)
    b)删除:
    i. void clear():清空容器
    j. boolean remove(Objec object):
    k. boolean removeAll(Collection c):
    c)判断:
    i. boolean contains(Object object):判断是否包含此元素
    j. boolean containsAll(Collection c):判断是否包含一堆元素
    k. boolean equals(Object object):比较此collection与指定对象是否相等
    m. boolean isEmpty():判断是否集合为空
    d)获取:
    h. Iterator iterator():取出
    i. int hashCode():返回此collection的哈希值
    j. int size():返回此collection中元素的个数
    k. boolean retainAll(Collection c):取交集
    m. Object toArray():返回此collection中所有元素的数组
    n. T[] toArray(T[] a):返回包含此collection中所有元素的数值。
    *****List集合子类及其方法
    (1)List接口是Collection接口的一个子接口。
    (2)List接口中的元素有如下特点(对角标的操作都是特有方法,因为有序):
    A:元素有序(存储顺序和取出顺序一致)
    B:元素可以重复
    (3)List接口中的特有方法
    A:add(int index,Object obj):在指定位置加入元素
    B:remove(int index):移除指定位置的元素
    C:set(int index,Object obj):修改指定位置的元素
    D:get(int index):获取指定位置的元素
    E:indexOf(Object obj):获取指定元素的位置
    F:subList(int start,int end):从一个大的List中截取一个小的List
    G:listIterator():返回一个List接口特有的迭代器
    (1)、ArrayList:
    |—>构造方法摘要:(少用,不是重点)
    ArrayList():构造一个初始容量为 10 的空列表。
    ArrayList(Collection

    public static int add(int x,int ...args) {
                    int sum = x;
                    for(int arg:args) {
                        sum += arg;
                    }
                    return sum;
                }
        **增强for循环代替了迭代器使用的不爽,简化书写
        **增强for循环局限性:
            对集合或者数组进行遍历时,只能取元素,不能对集合进行操作
    (4)基本数据类型的自动装箱和拆箱
        **基本数据类型
            byte    --->    Byte
            short   --->    Short
            int --->    Integer
            long    --->    Long
            float   --->    Float
            double  --->    Double
            char    --->    Character
            boolean --->    Boolean
        **例子:
            **装箱:自动把一个基本数据类型的数据装箱成一个该类型数据的对象引用
                Integer i = 3;(jdk1.5之前这样写是不行的,编译报错)
            **拆箱:自动把一个基本数据类型的对象引用拆箱成一个基本数据类型的数据,再参与运算
                Integer i = 12;
                sop(i+4);
            **享元模式:
    
    Integer num1 = 12;
                    Integer num2 = 12;
                    System.out.println(num1 == num2);//打印true
    
                    Integer num5 = Integer.valueOf(12);
                    Integer num6 = Integer.valueOf(12);
                    System.out.println(num5 == num6);//打印true
    
                    Integer num3 = 129;
                    Integer num4 = 129;
                    System.out.println(num3 == num4);//打印false
                为什么前面的返回true而后面的运算返回false呢?
                对于基本数据类型的整数,装箱成Integer对象时,如果该数值在一个字节内,(-128~127),
                一旦装箱成Integer对象后,就把它缓存到磁里面,当下次,又把该数值封装成Integer对象时
                会先看磁里面有没有该对象,有就直接拿出来用,这样就节省了内存空间。因为比较小的整数,
                用的频率比较高,就没必要每个对象都分配一个内存空间。
                这就是享元模式!比如26个英文字母,10个阿拉伯数字
    (5)枚举
        **为什么要有枚举?
            问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,
            但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。
    
            枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。
            枚举可以让编译器在编译时就可以控制源程序中填写的非法值,
            普通变量的方式在开发阶段无法实现这一目标。
        **用普通类如何实现枚举的功能?定义一个Weekday类来模拟实现:
            步骤:
                *私有化构造方法
                *每个元素分别用一个公有的静态成员变量表示(public static final)
                *可以有若干公有方法或抽象方法。采用抽象方法定义nextDay就将大量的if.else语句
                 转移成了一个个独立的类。
        **枚举的应用:
            举例:定义一个Weekday的枚举。
            扩展:枚举类的values,valueOf,name,toString,ordinal等方法
                 (记住,讲课时要先于自定义方法前介绍,讲课更流畅)
            总结:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象。 
                  例如可以调用WeekDay.SUN.getClass().getName和WeekDay.class.getName()。
        **枚举的高级应用:
            **枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
            **枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。
              把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。
            **带构造方法的枚举
              构造方法必须定义成私有的
              如果有多个构造方法,该如何选择哪个构造方法?
              枚举元素MON和MON()的效果一样,都是调用默认的构造方法。
            **带方法的枚举
              定义枚举TrafficLamp
              实现普通的next方法
              实现抽象的next方法:每个元素分别是由枚举类的子类来生成的实例对象,
              这些子类采用类似内部类的方式进行定义。增加上表示时间的构造方法     
            **枚举只有一个成员时,就可以作为一种单例的实现方式。     
    (6)泛型:
        **泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,
          编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,
          对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
          由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,
          就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
        **ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
            整个称为ArrayList<E>泛型类型
            ArrayList<E>中的E称为类型变量或类型参数
            整个ArrayList<Integer>称为参数化的类型
            ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
            ArrayList<Integer>中的<>念着typeof
            ArrayList称为原始类型
        **参数化类型与原始类型的兼容性:
            参数化类型可以引用一个原始类型的对象,编译报告警告,
            例如,Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
            原始类型可以引用一个参数化类型的对象,编译报告警告,
            例如,Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
        **参数化类型不考虑类型参数的继承关系:
            Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
            Vector<Object> v = new Vector<String>(); //也错误!
            编译器不允许创建泛型变量的数组。即在创建数组实例时,
            数组的元素不能使用参数化的类型,
            例如,下面语句有错误:
                Vector<Integer> vectorList[] = new Vector<Integer>[10];
        **泛型限定:
            **限定通配符的上边界:
                正确:Vector<? extends Number> x = new Vector<Integer>();
                错误:Vector<? extends Number> x = new Vector<String>();
            **限定通配符的下边界:
                正确:Vector<? super Integer> x = new Vector<Number>();
                错误:Vector<? super Integer> x = new Vector<Byte>();
            **提示:
                限定通配符总是包括自己。
                ?只能用作引用,不能用它去给其他变量赋值
                Vector<? extends Number> y = new Vector<Integer>();
                Vector<Number> x = y;
                上面的代码错误,原理与Vector<Object > x11 = new Vector<String>();相似,
                只能通过强制类型转换方式来赋值。
    

    五、IO流
    1、IO流概述
    (1)用来处理设备(硬盘,控制台,内存)间的数据。
    (2)java中对数据的操作都是通过流的方式。
    (3)java用于操作流的类都在io包中。
    (4)按照流操作的数据的类型不同:分为字节流和字符流。字符流是为了方便中文的操作而来的。
    (5)按照流的流向不同分为:输入流,输出流
    2、IO流常用基类:
    (1)字节流
    输出字节流:OutputStream:字节写入流抽象类
    |—>FileOutputStream:
    字节写入流
    |—>BufferedOutputStream:
    字节写入流缓冲区
    |—>PrintStream:
    打印流
    输入字节流:InputStream:字节读取流抽象类
    |—>FileInputStream:
    字节读取流
    |—>BufferedInputStream:
    字节读取流缓冲区
    (2)字符流
    输出字符流:Writer:字符写入流的抽象
    |—>FileWriter:
    字符写入流
    |—>BufferedWriter:
    字符写入流缓冲区
    |—>OutputStreamWriter:
    字符通向字节的转换流(涉及键盘录入时用)
    |—>OutputStreamWriter:
    打印流,可处理各种类型的数据
    输入字符流:Reader: 字符读取流的抽象类
    |—>FileReader:
    字符读取流
    |—>LineNumberReader:
    跟踪行号的缓冲字符读取流
    |—>BufferedReader:
    字符读取流缓冲区
    |—>InputStreamReader:
    字节通向字符的转换流(涉及键盘录入时用)
    (3)IO流常用基类方法摘要:
    **字节写入流:OutputStream:
    void close() 关闭此输出流并释放与此流有关的所有系统资源。
    void flush()刷新此输出流并强制写出所有缓冲的输出字节。
    abstract void write(int b) 将指定的字节写入此输出流。
    void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。
    void write(byte[] b, int off, int len)
    将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
    **字节读取流:InputStream:
    void close() 关闭此输入流并释放与该流关联的所有系统资源。
    int available() (特有方法!!)
    返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
    abstract int read() 从输入流中读取数据的下一个字节。
    int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
    int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。
    long skip(long n) 跳过和丢弃此输入流中数据的 n 个字节。

        **字符写入流:Writer:
            abstract  void close() 关闭此流,但要先刷新它。
            abstract  void flush() 刷新该流的缓冲。
            void write(int c) 写入单个字符。
            void write(char[] cbuf) 写入字符数组。          
            abstract  void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 
            void write(String str) 写入字符串。 
            void write(String str, int off, int len) 写入字符串的某一部分。 
    
        **字符读取流:Reader:
            abstract  void close() 关闭该流并释放与之关联的所有资源。
            int read() 读取单个字符。
            int read(char[] cbuf)  将字符读入数组
            abstract  int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。
            long skip(long n)  跳过字符。 
    

    3、IO流常用字节流基类的子类:
    **写入流:
    (1)FileOutputStream:
    **构造方法:
    FileOutputStream(String name)
    创建一个向具有指定名称的文件中写入数据的输出文件流。
    FileOutputStream(String name, boolean append)
    创建一个向具有指定 name 的文件中写入数据的输出文件流。
    FileOutputStream(File file)
    创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
    FileOutputStream(File file, boolean append)
    创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
    **方法摘要:
    public void flush()
    void close() 关闭此文件输出流并释放与此流有关的所有系统资源。
    void write(int b) 将指定字节写入此文件输出流。
    void write(byte[] b, int off, int len)
    将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
    void write(int b) 将指定字节写入此文件输出流。
    (2)BufferedOutputStream:
    **构造方法:
    BufferedOutputStream(OutputStream out)
    创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
    BufferedOutputStream(OutputStream out, int size)
    创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
    **方法摘要:
    void flush() 刷新此缓冲的输出流。
    void write(byte[] b, int off, int len)
    将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流。
    void write(int b) 将指定的字节写入此缓冲的输出流。
    (3)PrintStream:打印流,可将各种类型的数据原样打印,有自动刷新功能
    **构造方法:
    PrintStream(String fileName)
    创建具有指定文件名称且不带自动行刷新的新打印流。
    PrintStream(File file)
    创建具有指定文件且不带自动行刷新的新打印流。
    PrintStream(OutputStream out)
    创建新的打印流。
    PrintStream(OutputStream out, boolean autoFlush) (当autoFlush为true时具有自动刷新功能)
    创建新的打印流。
    **方法摘要:
    PrintStream append(char c)
    将指定字符添加到此输出流。
    void close()
    关闭流。
    void flush()
    刷新该流的缓冲。
    void print(各种类型的数据:)
    打印各种类型的数据
    void println(各种类型的数据:):自动换行
    打印各种类型的数据
    void write(byte[] buf, int off, int len)
    将 len 字节从指定的初始偏移量为 off 的 byte 数组写入此流。
    void write(int b)
    将指定的字节写入此流。

    **读取流:
    (1)FileInputStream:
        **构造方法:
        FileInputStream(String name) 
            通过打开一个到实际文件的连接来创建一个 FileInputStream,
            该文件通过文件系统中的路径名 name 指定。
        FileInputStream(File file) 
            通过打开一个到实际文件的连接来创建一个 FileInputStream,
            该文件通过文件系统中的 File 对象 file 指定。
        **方法摘要:
        int available() (字节读取流特有方法!!!)
            返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
        int read() 
            从此输入流中读取一个数据字节。 
        int read(byte[] b) 
            从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 
        int read(byte[] b, int off, int len) 
            从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。 
        long skip(long n) 
            从输入流中跳过并丢弃 n 个字节的数据。 
    (2)BufferedInputStream:
        **构造方法:
        BufferedInputStream(InputStream in) 
            创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 
        BufferedInputStream(InputStream in, int size) 
            创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。 
        **方法摘要:
        int available() (字节读取流特有方法!!!)
            返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。 
        int read() 
            参见 InputStream 的 read 方法的常规协定。 
        int read(byte[] b, int off, int len) 
            从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。 
        long skip(long n) 
            参见 InputStream 的 skip 方法的常规协定。
    

    4、字符流常用基类的子类
    **写入流:
    (1)FileWriter:
    **构造方法:
    FileWriter(String fileName)
    根据给定的文件名构造一个 FileWriter 对象。
    FileWriter(String fileName, boolean append)
    根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。
    FileWriter(File file)
    根据给定的 File 对象构造一个 FileWriter 对象。
    FileWriter(File file, boolean append)
    根据给定的 File 对象构造一个 FileWriter 对象。
    FileWriter(FileDescriptor fd)
    构造与某个文件描述符相关联的 FileWriter 对象。
    **方法摘要:跟Writer一样
    abstract void close() 关闭此流,但要先刷新它。
    abstract void flush() 刷新该流的缓冲。
    void write(int c) 写入单个字符。
    void write(char[] cbuf) 写入字符数组。
    abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
    void write(String str) 写入字符串。
    void write(String str, int off, int len) 写入字符串的某一部分。
    (2)BufferedWriter:
    **构造方法:
    BufferedWriter(Writer out)
    创建一个使用默认大小输出缓冲区的缓冲字符输出流。
    BufferedWriter(Writer out, int sz)
    创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
    **方法摘要:
    void close()
    关闭此流,但要先刷新它。
    void flush()
    刷新该流的缓冲。
    void newLine()
    写入一个行分隔符。
    void write(char[] cbuf, int off, int len)
    写入字符数组的某一部分。
    void write(int c)
    写入单个字符。
    void write(String s, int off, int len)
    写入字符串的某一部分。
    (3)OutputStreamWriter:字节通向字符的转换流
    **构造方法:
    OutputStreamWriter(OutputStream out)
    创建使用默认字符编码的 OutputStreamWriter。
    **方法摘要:
    void write(char[] cbuf, int off, int len)
    写入字符数组的某一部分。
    void write(int c)
    写入单个字符。
    void write(String str, int off, int len)
    写入字符串的某一部分。
    (4)PrintWriter:
    **构造方法:
    PrintWriter(String fileName)
    创建具有指定文件名称且不带自动行刷新的新 PrintWriter。
    PrintWriter(File file)
    使用指定文件创建不具有自动行刷新的新 PrintWriter。
    PrintWriter(Writer out)
    创建不带自动行刷新的新 PrintWriter。
    PrintWriter(Writer out, boolean autoFlush)
    创建新 PrintWriter。
    PrintWriter(OutputStream out)
    根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。
    PrintWriter(OutputStream out, boolean autoFlush)
    通过现有的 OutputStream 创建新的 PrintWriter。

        **方法摘要:
        PrintWriter append(char c) 
            将指定字符添加到此 writer。 
        void print(各种类型的数据:) 
            打印各种类型的数据 
        void println(各种类型的数据:):自动换行
            打印各种类型的数据
        void write(char[] buf) 
            写入字符数组。 
        void write(char[] buf, int off, int len) 
            写入字符数组的某一部分。 
        void write(int c) 
            写入单个字符。 
        void write(String s) 
            写入字符串。 
        void write(String s, int off, int len) 
            写入字符串的某一部分。 
    **读取流:
    (1)FileReader:
        **构造方法:
        FileReader(String fileName) 
            在给定从中读取数据的文件名的情况下创建一个新 FileReader。
        FileReader(File file) 
            在给定从中读取数据的 File 的情况下创建一个新 FileReader。 
        FileReader(FileDescriptor fd) 
            在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。 
        **方法摘要:和Reader基类方法一致:
        abstract  void close() 关闭该流并释放与之关联的所有资源。
        int read() 读取单个字符。
        int read(char[] cbuf)  将字符读入数组
        abstract  int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。
        long skip(long n)  跳过字符。 
    (2)BufferedReader:
        **构造方法:
        BufferedReader(Reader in) 
            创建一个使用默认大小输入缓冲区的缓冲字符输入流。
        **方法摘要:
        int read() 
            读取单个字符。 
        int read(char[] cbuf, int off, int len) 
            将字符读入数组的某一部分。 
        String readLine() 
            读取一个文本行。 
    (3)InputStreamReader:字符通向字节的桥梁:
        **构造方法:
        InputStreamReader(InputStream in) 
            创建一个使用默认字符集的 InputStreamReader。
        **方法摘要:
        int read() 读取单个字符。
        int read(char[] cbuf)  将字符读入数组
        abstract  int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。
        long skip(long n)  跳过字符。
    (4)LineNumberReader:
        **构造方法:
        LineNumberReader(Reader in) 
            使用默认输入缓冲区的大小创建新的行编号 reader。
        **方法摘要:
        int read() 
            读取单个字符。 
        int read(char[] cbuf, int off, int len) 
            将字符读入数组中的某一部分。 
        String readLine() 
            读取文本行。
        long skip(long n) 
            跳过字符。 
        int getLineNumber() 
            获得当前行号。 
        void setLineNumber(int lineNumber) 
            设置当前行号。 
    

    6、IO流常见需求:
    **字符流:
    (1)需求1:在硬盘上创建一个文件并写入信息

    用字符写入流:FileWriter
            FileWriter fw = new FileWriter("g:\\filewriter.txt");
            fw.write("输入信息");
            fw.write("也可以写入字符数组".toCharArray());
            fw.flush();
            fw.close();
    (2)需求2:在原有文件上续写数据
    
    FileWriter fw = new FileWriter("g:\\filewriter.txt",true);
            fw.write("还可以续写信息");
            fw.write("也可以写入字符数组".toCharArray());
            fw.flush();
            fw.close();
    (3)需求3:读取硬盘上的文本文件,并将数据打印在控制台
        FileReader fr = new FileReader("g:\\filewriter.txt");
        **第一种读取方法:一个一个字节的读
    
    int ch = 0;
            ch = fr.read();
            sop((char)ch);
            fr.close();
        **第二种读取方法:利用数组来提高效率
    
    char[] buf = new char[1024];
            int len = 0;
            while((len = fr.read(buf))!=-1)
            {
                sop(new String(buf,0,len));
            }
            fr.close();
    (4)需求4:拷贝文本文件
        利用缓冲区提高数据读写效率
        (无缓冲区就相当于一滴一滴的喝水,有缓冲区就相当于一杯一杯的喝水)
    
    BufferedReader bufr = new BufferedReader(new FileReader("g:\\filewriter.txt"));
            BufferedWriter bufw = new BufferedWriter(new FileWriter("d:\\copyfilewriter.txt"));
            String line = null;
            while((line = bufr.readLine())!=null)
            {
                burw.write(line);
                bufw.newLine();
                bufw.flush();
            }
            bufr.close();
            bufw.close();

    **字节流:字节流写入时没有刷新
    (1)需求1:在硬盘上创建一个文件并写入信息(字节流写入时没有刷新)
    FileOutputStream fos = new FileOutputStream(“g:\filestream.txt”);
    fos.write(97);//写入一个字节,int:97代表写入char:a
    fos.write(“也可以写入字节数组”.getBytes());//通常使用此种方式写入,直观!
    fos.close();
    (2)需求2:在硬盘已有文件上续写数据(字节流写入时没有刷新)
    FileOutputStream fos = new FileOutputStream(“g:\filestream.txt”,true);
    fos.write(“创建字节写入流时,传进去一个true参数就可以继续写入信息”.getBytes());
    fos.close();
    (3)需求3:读取硬盘上的文件
    FileInputStream fis = new FileInputStream(“g:\filestream.txt”);
    **第一种读法:一个字节一个字节的读(此种读法慢)

    int ch = 0;
            while((ch = fis.read())!=-1)
            {
                sop((char)ch);
            }
        **第一种读法:利用字节数组读(此种读法效率有一定提高)
    
    byte[] buf = new byte[1024];
            int len = 0;
            while((len = fis.read())!=-1)
            {
                sop(new String(buf,0,len));
            }
    (4)需求4:拷贝字节文件,如图片或者MP3或者电影
        **第一种拷贝:不带缓冲区(慢,还是效率问题)
    
    FileInputStream fis = new FileInputStream("g:\\1.mp3");
            FileOutputStream fos = new FileOutputStream("g:\\copy1.mp3");
            byte[] buf = new byte[1024];
            int len = 0;
            while((len = fis.read(buf))!=-1)
            {
                fos.(buf,0,len);//字节流写入无需刷新
            }
            fis.close();
            fos.close();
        **第二种拷贝:带缓冲区,高效
    
    BufferedInputStream bufi = new BufferedInputStream(new FileInputStream("g:\\1.mp3"));
            BufferedOutputStream bufo = new BufferedOutputStream(new FileOutputStream("g:\\copy1.mp3"));
            int ch = 0;
            while((ch = bufi.read())!=-1)
            {
                bufo.write(ch);
            }
            bufi.close();
            bufo.close();

    **转换流:
    (1)需求1:读取一个键盘录入
    InputStream in = System.in;//创建一个键盘录入流,流不关则可以一直录入
    int by1 = in.read();//一次读一个字节
    int by2 = in.read();//一次读一个字节
    sop(by1);//假设键盘录入的是abcd,则打印a
    sop(by2);//假设键盘录入的是abcd,则打印b
    in.close();
    (2)需求2:键盘录入一行数据打印一行数据,如果录入的是over则结束录入

    InputStream in = System.in;
            StringBuilder sb = new StringBuilder();
            while(true)
            {
                int ch = in.read();
                if(ch=='\r')
                    continue;
                if(ch=='\n')
                {
                    String line = sb.toString();
                    if("over".equals(line))
                        break;
                    sop(line.toUpperCase());//输出大写
                    sb.delete(0.sb.length());//清除上一行录入的数据
    
                }
                else
                    sb.append((char)ch);
            }
            in.close();
    (3)需求3:发现需求2中其实就是读一行的原理,故引入字节通向字符的桥梁:InputStreamReader
        为提高效率加入缓冲区:
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        String line = null;
        while((line = bufr.readLine())!=null)
        {
            if("over".equals(line))
                break;
            sop(line.toUpperCase());//输出大写
        }
        bufr.close();
    (4)需求4:键盘录入数据并打印到控制台
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bufw = new BufferedWriter(new OntputStreamWriter(System.out));
        String line = null;
        while((line = bufr.readLine())!=null)
        {   
            if("over".equals(line))
                break;
            bufw.write(line.toUpperCase());
            bufw.newLine();
            bufw.flush();   
        }
        bufr.close();
        bufw.close();
    (5)需求5:将键盘录入的数据存储到硬盘文件
        则只需将(4)中的
        BufferedWriter bufw = new BufferedWriter(new OntputStreamWriter(System.out));
        改为:
        BufferedWriter bufw = new BufferedWriter(new OntputStreamWriter(new FileWriter("g:\\demo.txt")));
        即:
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bufw = new BufferedWriter(new OntputStreamWriter(new FileWriter("g:\\demo.txt")));
        String line = null;
        while((line = bufr.readLine())!=null)
        {   
            if("over".equals(line))
                break;
            bufw.write(line.toUpperCase());
            bufw.newLine();
            bufw.flush();   
        }
        bufr.close();
        bufw.close();
    (6)需求6:将硬盘文件的数据打印到控制台
        则只需将(4)中的
    

    BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

        改为:BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileReader("g:\\demo.txt")));
        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileReader("g:\\demo.txt")));
        BufferedWriter bufw = new BufferedWriter(new OntputStreamWriter(System.out));
        String line = null;
        while((line = bufr.readLine())!=null)
        {   
            if("over".equals(line))
                break;
            bufw.write(line.toUpperCase());
            bufw.newLine();
            bufw.flush();   
        }
        bufr.close();
        bufw.close();
    

    7、流操作的规律:
    **流操作的难点:流对象很多,不知道具体用哪个
    **规律:
    (1)第一步:先明确源和目的
    源:
    文本:用Reader
    字节:用InputStream
    目的:
    文本:用Writer
    字节:用OutputStream
    (2)第二步:明确是不是纯文本
    是:用字符流;
    不是:用字节流
    (3)第三步:明确流体系后,通过设备来明确具体使用哪个流对象
    源设备:
    键盘:System.in
    硬盘:文件流File
    内存:数组流ArrayStream
    目的设备:
    键盘:System.out
    硬盘:文件流File
    内存:数组流ArrayStream
    8、File类
    构造方法:
    File(String pathname)
    通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
    File(String parent, String child)
    根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
    File(File parent, String child)
    根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
    方法摘要:
    (1)创建:
    boolean createNewFile()
    当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
    boolean mkdir()
    创建一级文件夹
    boolean mkdirs()
    创建多级文件夹
    (判断):
    boolean canExecute()
    测试应用程序是否可以执行此抽象路径名表示的文件。
    boolean canRead()
    测试应用程序是否可以读取此抽象路径名表示的文件。
    boolean canWrite()
    测试应用程序是否可以修改此抽象路径名表示的文件。
    int compareTo(File pathname)
    按字母顺序比较两个抽象路径名。
    boolean isAbsolute()
    测试此抽象路径名是否为绝对路径名。
    boolean isDirectory()
    测试此抽象路径名表示的文件是否是一个目录。
    boolean isFile()
    测试此抽象路径名表示的文件是否是一个标准文件。
    boolean isHidden()
    测试此抽象路径名指定的文件是否是一个隐藏文件。
    boolean exists()
    测试此抽象路径名表示的文件或目录是否存在。
    (3)获取:
    String getParent()
    返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
    File getParentFile()
    返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。
    String getName()
    返回由此抽象路径名表示的文件或目录的名称。
    String getPath()
    将此抽象路径名转换为一个路径名字符串。
    String getAbsolutePath()
    返回此抽象路径名的绝对路径名字符串。
    File getAbsoluteFile()
    返回此抽象路径名的绝对路径名形式。
    (4)删除:
    boolean delete()
    删除此抽象路径名表示的文件或目录。
    oid deleteOnExit()
    在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。
    (5)获取全部:(非常重要!!!)
    String[] list()
    返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
    String[] list(FilenameFilter filter)
    返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。
    File[] listFiles()
    返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
    File[] listFiles(FileFilter filter)
    返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。

    ****FilenameFilter接口只有一个方法:
        boolean accept(File dir, String name) 
            测试指定文件是否应该包含在某一文件列表中。 
    ****FileFilter接口只有一个方法:
        boolean accept(File dir, String name) 
            测试指定文件是否应该包含在某一文件列表中。
    

    8、File类常见需求:
    (1)文件名过滤:列出给定目录的所有.java文件

    public void showFileName(File file)
            {
                String[] filenames = file.list(new FilenameFilter()//匿名内部类
                {
                    public boolean accept(File dir,String name)//复写唯一方法
                    {
                        return name.endsWith(".java");//列出所有.java文件
                    }
                });
            }
    (2)列出指定目录下的所有文件和文件夹(递归)
    **示例1:不带层次递归:
    
    public static void showDir(File dir)
        {
            File[] files = dir.listFile();
            for(int i = 0;i<files.length;i++)
            {
                if(files[i].isDirectory&&!files[i].isHidden())
                    showDir(files[i]);
                else
                    sop(files[i]);
            }
        }
    **示例2:带层次递归:
    
    public static void showDir(File dir,int level)
        {
            sop(getLevel(level)+C);//进来先打印层次和目录
            level++;
            File[] files = dir.listFile();
            for(int i = 0;i<files.length;i++)
            {
                if(files[i].isDirectory&&!files[i].isHidden())
                    showDir(files[i]);
                else
                    sop(getLevel(level)+files[i]);//是文件就打印层次和目录
            }
        }
        public static String getLevel(int level)
        {
            sop("|--");
            StringBuilder sb = new StringBuilder();
            for(int i=0;i<level;i++)
            {
                sb.inset(0."|  ")
            }
            return sb.toString();
        }
    (3)需求:删除带内容的目录:
    
    public static void removeDir(File dir)
        {
            File[] files = file.listFile();
            for(int i = 0;i<files.length;i++)
            {
                if(files[i].isDirectory&&!files[i].isHidden())
                    removeDir(files[i]);//如果是文件夹则继续调用函数
                else//如果是文件则删除。注意删除的时候打印删除的结果,防止误删或者重删的情况
                    sop(files[i].toString()+"::"+files[i].delete());
            }
            sop(dir+"::"+dir.delete());
        }
    (4)需求:将制定目录下的java文件的绝对路径存储到文本文件中。
       思路:
       **对指定目录进行递归
       **获取递归过程中所有java文件的路径
       **将这些路径存储到集合中
       **将集合中的数据写入文件中
     //对指定目录进行递归并将所以Java文件存储到集合中
    
    public static void getFileName(File file,ArrayList<File> arraylist){
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                if(files[i].isDirectory()&&!files[i].isHidden()){
                    getFileName(files[i],arraylist);
                }else{
                    if(files[i].getName().endsWith(".java")){
                        arraylist.add(files[i]);
                    }
                }
            }
        }
        //将集合中所有数据存储到新文件中
        public static void saveFileToNewDir(ArrayList<File> arraylist,File newDir){
            BufferedWriter bufw = null;
            try {
                bufw = new BufferedWriter(new FileWriter(newDir));
                for (File file : arraylist) {
                    String fileAbsolutePath = file.getAbsolutePath();
                    bufw.write(fileAbsolutePath);
                    bufw.newLine();
                    bufw.flush();   
                }
            } catch (Exception e) {
                System.out.println("文件写入失败");
            }finally{
                try {
                    if(bufw!=null)
                        bufw.close();
                } catch (Exception e2) {
                    System.out.println("文件写入流关闭失败");
                }
            }
        }

    9、Properties
    (1)Properties是HashTable的子类,具备Map集合的特点,里面存储的是键值对
    (2)Properties是IO流合集合相结合的集合容器
    (3)Properties的特点是可以用于存储键值对形式的配置文件
    (4)构造方法:
    Properties()
    创建一个无默认值的空属性列表。
    Properties(Properties defaults)
    创建一个带有指定默认值的空属性列表。
    (5)方法摘要:
    Object setProperty(String key, String value)
    调用 Hashtable 的方法 put。
    String getProperty(String key)
    用指定的键在此属性列表中搜索属性。
    void load(InputStream inStream)
    从输入流中读取属性列表(键和元素对)。
    void load(Reader reader)
    按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
    void list(PrintStream out)
    将属性列表输出到指定的输出流。
    void list(PrintWriter out)
    将属性列表输出到指定的输出流。
    void store(OutputStream out, String comments)
    以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,
    将此 Properties 表中的属性列表(键和元素对)写入输出流。
    void store(Writer writer, String comments)
    以适合使用 load(Reader) 方法的格式,将此 Properties 表中的
    属性列表(键和元素对)写入输出字符。
    Set stringPropertyNames()
    返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中
    未找到同名的键,则还包括默认属性列表中不同的键
    (6)Properties代码示例:

    public static void show()
        {
            Properties prop = new Properties();
            prop.setProperty("张三","26");
            prop.setProperty("李四","30");
            prop.setProperty("王五","35");
            sop(prop);
            String value = prop.getProperty("张三");
    
            Set<String> keys = prop.stringPropertyName();
            for(String key : values)
            {
                sop(key+":"+prop.getPropety(key));
            }
        }

    (7)需求:记录应用程序的使用次数,如果使用次数已到,则提示用户注册。
    思路:
    **第一次使用时建立一个配置文件用于记录使用次数
    **每次使用都加载该配置文件,并先判断已使用次数
    **每次使用完使用次数加1,写入配置文件

        public static void main(String[] args) throws IOException{
            Properties prop = new Properties();//定义Properties,用来和IO流结合
            File file = new File("library\\time.ini");//配置文件
            if(!file.exists())
                file.createNewFile();//如果文件不存在则创建文件(用于第一次使用时创建文件)
            FileInputStream fis = new FileInputStream(file);//定义字节读取流,读取配置文件中记录的使用次数
            prop.load(fis);//载入流,以获取文件中配置的键值对
            int count = 0;//定义使用次数
            String countValue = prop.getProperty("time");//通过键获取值
            if(countValue!=null){//第一次时countValue为null
                count = Integer.parseInt(countValue);//将字符串次数变成数字次数
                if(count>3){
                    System.out.println("您使用次数已到,继续使用请注册!");
                    return;
                }
            }
            count++;//如果使用次数未到则次数加1
            prop.setProperty("time", count+"");//配置新的键值对
            FileWriter fos = new FileWriter(file);
            prop.store(fos, "这是应用程序使用次数的配置文件");//将新的键值对写入文件
            fis.close();
            fos.close();    
        }

    10、IO中的其他流:
    (1)打印流:
    **PrintWriter:字符打印流
    **构造方法:
    PrintWriter(String fileName)
    创建具有指定文件名称且不带自动行刷新的新 PrintWriter。
    PrintWriter(File file)
    使用指定文件创建不具有自动行刷新的新 PrintWriter。
    PrintWriter(Writer out)
    创建不带自动行刷新的新 PrintWriter。
    PrintWriter(Writer out, boolean autoFlush)
    自动刷新
    PrintWriter(OutputStream out)
    根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。
    PrintWriter(OutputStream out, boolean autoFlush)
    自动刷新
    **方法摘要:
    PrintWriter append(char c)
    将指定字符添加到此 writer。
    void close()
    关闭该流并释放与之关联的所有系统资源。
    void flush()
    刷新该流的缓冲。
    void print(Object obj)
    打印对象。
    void print(String s)
    打印字符串。
    void println()
    通过写入行分隔符字符串终止当前行。
    **PrintStream:字节打印流
    **构造方法:
    PrintStream(String fileName)
    创建具有指定文件名称且不带自动行刷新的新打印流。
    PrintStream(File file)
    创建具有指定文件且不带自动行刷新的新打印流。
    PrintStream(OutputStream out)
    创建新的打印流。
    PrintStream(OutputStream out, boolean autoFlush)
    创建新的打印流。
    **方法摘要:
    PrintWriter append(char c)
    将指定字符添加到此 writer。
    void close()
    关闭该流并释放与之关联的所有系统资源。
    void flush()
    刷新该流的缓冲。
    void print(Object obj)
    打印对象。
    void print(String s)
    打印字符串。
    void println()
    通过写入行分隔符字符串终止当前行。
    (2)对象系列化:
    **对象实体化:找一个介质,能长期的存储对象。
    **对象的属性在Java程序中,都是存在于对内存中,随着对象的消失而消失,
    而ObjectOutputStream可以将对象实体化
    **Serializable接口没有一个方法,也就是说其是一个标记接口。比如盖章的猪肉才是安全的。
    **只有实现Serializable接口的子类才能被ObjectOutputStream系列化写入流,当某个
    类实现该接口后,会被Java自动分配UID号,以便编译器识别,区分不同对象。
    **用ObjectOutputStream系列化的对象存储到文件后,该文件是乱码,也就是不可读的
    的用ObjectInputStream读取该类对象的属性。
    **由于对象是有Java给对象分配相应的UID号,而UID号是根据对象的属性不同而分配的。
    当一个类对象被系列化到文件后,如果该类改动了对象的属性,比如将某个成员变量变成私有
    则该对象再用ObjectInputStream读取时会报异常,也就是说该系列化到文件的对象不能再被使用了
    那么,要想继续使用属性被改动后的对象,我们可以自定义给对象分配UID号,让UID号不随对象的属性
    变化而变化。
    自定义对象分配UID方法如下:
    public static final long serialVersion UID = 43L;
    **注意:
    静态不能被系列化,因为静态成员变量实在内存的方法区,而ObjectOutputStream只能
    对对内存里面的数据进行系列化
    被transient修饰的非静态成员变量也不能被系列化
    被系列化的对象存储到文件中,该文件是不可读的,所以该文件的扩展名一般
    不写成.txt,通常后缀名写.object
    **ObjectOutputStream
    **ObjectInputStream
    (3)管道流:
    PipedInputStream
    PipedOutputStream
    (4)随机访问文件:RandomAccess(重要!!!)
    **自身具备读写方法(很牛逼!又可以读又可以写)
    **通过skipByte(int x)和seek(int x)来达到随机访问文件
    **该类不是IO体系子类,而是直接继承Object,但它是IO包中的成员,因为它具备读写方法
    **该类内部封装了数组,而且通过指针对数组的元素进行操作,可以通过getFilePoint获取指针位置
    同时可以通过seek改变指针位置
    **该类完成读写的原理是内部封装了字节输入输出流
    **通过该类的构造看出,该类只能操作文件,而且操作的文件只能有固定模式:
    “r”:只读
    “rw”:读写
    “rws”:
    “red”:
    **构造方法:
    RandomAccessFile(File file, String mode)
    创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
    RandomAccessFile(String name, String mode)
    创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
    **方法摘要:
    void write(byte[] b)
    将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。
    void write(byte[] b, int off, int len)
    将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。
    void write(int b)
    向此文件写入指定的字节。
    int read()
    从此文件中读取一个数据字节。
    int read(byte[] b)
    将最多 b.length 个数据字节从此文件读入 byte 数组。
    int read(byte[] b, int off, int len)
    将最多 len 个数据字节从此文件读入 byte 数组。
    String readLine()
    从此文件读取文本的下一行。
    long getFilePointer()
    返回此文件中的当前偏移量。
    long length()
    返回此文件的长度。
    void seek(long pos)
    设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
    (4)操作基本数据类型的流对象:DateStream
    (5)操作字节数组流:
    ByteArrayInputStream
    ByteArrayOutputStream
    11、IO流转换流的字符编码
    (1)字符流的出现为了方便操作字符,更重要的是加入了编码转换
    (2)通过子类转换流来完成
    InputStreamReander
    OutputStreamWriter
    (3)在两个子类对象进行构造的时候可以加入编码表
    (4)编码表:
    将各个国家的文字用二进制数字表示并一一对应,形成一张表,这就是编码表
    (5)常见的编码表:
    **ASCII:美国标准信息交换码,用一个字节的七位表示
    **ISO8859-1:拉丁码表,欧洲码表,用一个字节的八位表示
    **GB2312:中文编码表,用两个字节表示
    **GBK:中文编码表升级,融合录入更多的中文字符,用两个字节表示,为避免和老美重复
    两字节的最高位都是1,即汉字都是用负数表示
    **Unicode:国际标准码,融合了多种文字,所有文字都用两个字节表示
    **UTF-8:用一个字节到三个字节表示。
    注:Unicode能识别中文,UTF-8也能识别中文,但两种编码表示一个汉字所用的字节数不同
    Unicode用两个字节,UTF-8用三个字节,故涉及到编码转换。
    (6)在流中涉及编码表的转换只有转换流:
    InputStreamReander
    OutputStreamWriter
    (7)代码示例:

        public static void write() throws IOException
            {
                OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
                osw1.write("你好");
                osw1.close();
    
                OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("utf-8.txt"),"UTF-8");
                osw2.write("你好");
                osw2.close();
            }
            public static void read() throws IOException
            {
                InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"GBK");
                byte[] buf = new byte[1024];
                int len = isr.read(buf);
                sop(new String(buf,0,len));
            }
    (8)编码解码
        编码:字符串变成字节数组:String-->getBytes()-->byte[]()
        解码:字节数组变成字符串:byte[]-->new String(byte[],0,len)-->String
    (9)代码示例:
    
    public static void main(String[] args)
            {
                //编码解码1:默认编码
                String str1 = "你好";
                byte[] buf1 = str1.getBytes();//默认解码:Unicode,四个字节
    
                //编码解码2:指定编码
                String str2 = "你好";
                byte[] buf2 = str2.getBytes("UTF-8");//指定解码:UTF-8,六个字节
    
    
                //编码解码3:编码正确解码错误
                String str3 = "你好";
                byte[] buf3 = str3.getBytes("GBK");//指定编码:GBK,四个字节
                String str3 = new String(buf3,"ISO8859-1");//错误解码
    
                //编码解码4:错误编码正确解码
                String str4 = "你好";
                byte[] buf4 = str4.getBytes("ISO8859-1");//错误编码
                String str4 = new String(buf4,"GBK");//正确解码,读不出来
    
                //编码解码5:编码对了,但是解码错误了,怎么办呢?
                //此时可以将错误的解码再错编回去,载用正确编码解码
                String str5 = "你好";
                byte[] buf5 = str5.getBytes("GBK");//正确编码
                String str6 = new String(buf5,"ISO8859-1");//错误解码,读不出来
                byte[] buf6 = str6.getBytes("ISO8859-1");//再错误编码
                String str7 = new String(buf6,"GBK");//再正确解码,这样就可以读出来了
            }

    六、网络编程:
    1、网络编程概述
    (1)网络模型
    OSI参考模型
    TCP/IP参考模型
    (2)网络通讯要素
    IP地址
    端口号
    传输协议
    (3)网络通讯前提:
    **找到对方IP
    **数据要发送到指定端口。为了标示不同的应用程序,所以给这些网络应用程序都用数字进行标示
    。这个表示就叫端口。
    **定义通信规则。这个规则称为通信协议,国际组织定义了通用协议TCP/IP
    (4)计算机网络:
    是指将地理位置不同的具有独立功能的多台计算机及其外部设备,
    通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,
    实现资源共享和信息传递的计算机系统。
    (5)IP地址:
    IP地址 = 网络号码+主机地址

        A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码
        B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码
        C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码
    
        特殊地址:
        127.0.0.1 回环地址,可用于测试本机的网络是否有问题. ping 127.0.0.1   
        ipconfig:查看本机IP地址
        xxx.xxx.xxx.0 网络地址
        xxx.xxx.xxx.255 广播地址
    
        A类  1.0.0.1---127.255.255.254   10.X.X.X是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址)                          (2)127.X.X.X是保留地址,用做循环测试用的。
        B类  128.0.0.1---191.255.255.254 172.16.0.0---172.31.255.255是私有地址。169.254.X.X是保留地址。
        C类  192.0.0.1---223.255.255.254 192.168.X.X是私有地址
        D类  224.0.0.1---239.255.255.254     
        E类  240.0.0.1---247.255.255.254
    (6)各种网络分类方式
        A:按网络覆盖范围划分
          局域网(几米至10公里以内)   城域网(10~100公里)   广域网(几百公里到几千公里)   国际互联网
        B:按网络拓扑结构划分
          总线型网络   星形网络   环型网络   树状网络   混合型网络
        C:按传输介质划分
          有线网   无线网
        D:按网络使用性质划分
          公用网   专用网
    (7)虚拟专用网络(Virtual Private Network ,简称VPN)指的是在公用网络上建立专用网络的技术。
        其之所以称为虚拟网,主要是因为整个VPN网络的任意两个节点之间的连接并没有传统专网
        所需的端到端的物理链路,而是架构在公用网络服务商所提供的网络平台,如Internet、
        ATM(异步传输模式〉、Frame Relay (帧中继)等之上的逻辑网络,
        用户数据在逻辑链路中传输。它涵盖了跨共享网络或公共网络的封装、
        加密和身份验证链接的专用网络的扩展。VPN主要采用了隧道技术、加解密技术、
        密钥管理技术和使用者与设备身份认证技术。
    (8)网络模型:
        ****OSI模型
            应用层
            表示层
            会话层
            传输层
            网络层
            数据连接层
            物理层
        ****TCP/IP模型
            应用层
            传输层
            网际层
            主机至网络层
    

    2、TCP和UDP
    (1)UDP和TCP的区别:
    UDP
    将数据及源和目的封装成数据包中,不需要建立连接
    每个数据报的大小在限制在64k内
    因无连接,是不可靠协议
    不需要建立连接,速度快
    TCP
    建立连接,形成传输数据的通道。
    在连接中进行大数据量传输
    通过三次握手完成连接,是可靠协议
    必须建立连接,效率会稍低
    注:三次握手:
    第一次:我问你在么?
    第二次:你回答在。
    第三次:我反馈哦我知道你在。
    3、Socket(UDP传输)
    **Socket就是为网络服务提供的一种机制。
    **通信的两端都有Socket。
    **网络通信其实就是Socket间的通信。
    **数据在两个Socket间通过IO传输。
    **玩Socket主要就是记住流程,代码查文档就行
    (1)UDP传输:DatagramSocket与DatagramPacket
    **发送端:
    建立DatagramSocket服务;
    提供数据,并将数据封装到字节数组中;
    创建DatagramPacket数据包,并把数据封装到包中,同时指定IP和接收端口
    通过Socket服务,利用send方法将数据包发送出去;
    关闭DatagramSocket和DatagramPacket服务。
    **接收端:
    建立DatagramSocket服务,并监听一个端口;
    定义一个字节数组和一个数据包,同时将数组封装进数据包;
    通过DatagramPacket的receive方法,将接收的数据存入定义好的数据包;
    通过DatagramPacke关闭t的方法,获取发送数据包中的信息;
    关闭DatagramSocket和DatagramPacket服务。
    DatagramSocket与DatagramPacket方法摘要:
    *****DatagramSocket
    构造方法:
    DatagramSocket()
    构造数据报套接字并将其绑定到本地主机上任何可用的端口。
    DatagramSocket(int port)
    创建数据报套接字并将其绑定到本地主机上的指定端口。
    DatagramSocket(int port, InetAddress laddr)
    创建数据报套接字,将其绑定到指定的本地地址。
    方法摘要:
    void close()
    关闭此数据报套接字。
    InetAddress getInetAddress()
    返回此套接字连接的地址。
    InetAddress getLocalAddress()
    获取套接字绑定的本地地址。
    int getPort()
    返回此套接字的端口。
    void receive(DatagramPacket p)
    从此套接字接收数据报包。
    void send(DatagramPacket p)
    从此套接字发送数据报包。
    ****DatagramPacket
    构造方法:
    DatagramPacket(byte[] buf, int length)
    构造 DatagramPacket,用来接收长度为 length 的数据包。
    DatagramPacket(byte[] buf, int length, InetAddress address, int port)
    构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
    InetAddress getAddress()
    返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
    byte[] getData()
    返回数据缓冲区。
    int getLength()
    返回将要发送或接收到的数据的长度。
    int getPort()
    返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
    代码示例:
    **发送端:

        class UDPSend
            {
                public static void main(String[] args) throws Exception
                {
                    DatagramSocket ds = new DatagramSocket();
                    byte[] buf = "这是UDP发送端".getBytes();
                    DatagramPacket dp = new DatagramPacket(
                        buf,buf.length,InetAddress.getByName("192.168.1.253"),10000);
                    ds.send(dp);
                    ds.close();
                }
            }
        ****接收端
    
        class UDPRece
            {
                public static void main(String[] args) throws Exception
                {
                    DatagramSocket ds = new DatagramSocket(10000);
                    byte[] buf = new byte[1024];
                    DatagramPacket dp = new DatagramPacket(buf,buf.length);
                    ds.receive(dp);//将发送端发送的数据包接收到接收端的数据包中
                    String ip = dp.getAddress().getHosyAddress();//获取发送端的ip
                    String data = new String(dp.getData(),0,dp.getLength());//获取数据
                    int port = dp.getPort();//获取发送端的端口号
                    sop(ip+":"+data+":"+port);
                    ds.close();
                }
            }
        需求1:UDP键盘录入数据,并发送给接收端
        发送端:
    
        class UDPSend
            {
                public static void main(String[] args) throws Exception
                {
    
                    DatagramSocket ds = new DatagramSocket();
                    BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
                    String line = null;
                    while((line = bufr.readLine())!=null)
                    {
                        if("886".equals(line))
                            break;
                        byte[] buf = line.getBytes();
                        DatagramPacket dp = new DatagramPacket(
                            buf,buf.length,InetAddress.getByName("192.168.1.253"),10000);
                        ds.send(dp);
                    }
                    ds.close();
                }
    
            }
        接收端:
    
        class UDPRece
            {
                public static void main(String[] args) throws Exception
                {
                    DatagramSocket ds = new DatagramSocket(10000);
                    while(true)
                    {
                        byte[] buf = new byte[1024];
                        DatagramPacket dp = new DatagramPacket(buf,buf.length);
                        ds.receive(dp);//将发送端发送的数据包接收到接收端的数据包中
                        String ip = dp.getAddress().getHosyAddress();//获取发送端的ip
                        String data = new String(dp.getData(),0,dp.getLength());//获取数据
                        int port = dp.getPort();//获取发送端的端口号
                        sop(ip+":"+data+":"+port);
                        ds.close();
                    }
                }
    
            }
        需求2:编写简单的聊天工具
        思路:
            使用多线程技术
        发送端:
    
            class UDPSend implements Runnable
            {
                private DatagramSocket ds;
                public UDPSend(){}
                public UDPSend(DatagramSocket ds)
                {
                    this.ds=ds;
                }
                public void run()
                {
                    try
                    {
                        BufferedReader bufr = new BufferedReader(
                                    new InputStreamReader(System.in));
                        String line = null;
                        while((line = bufr.readLine())!=null)
                        {
                            if("886".equals(line))
                                break;
                            byte[] buff = line.getBytes();
                            DatagramPacket dp = new DatagramPacket(
                            buf,buf.length,InetAddress.getByName("192.168.1.253"),10000);
                            ds.send(dp);
                        }
                    }
                    catch(Exception e)
                    {
                        throw new RuntimeException("发送失败");
                    }
                }
            }
        接收端:
    
            class UDPRece implements Runnable
            {
                private DatagramSocket ds;
                public UDPSend(){}
                public UDPSend(DatagramSocket ds)
                {
                    this.ds=ds;
                }
                public void run()
                {
                    try
                    {
                        while(true)
                        {   
                            byte[] buf = new byte[1024];
                            DatagramPacket dp = new DatagramPacket(buf,buf.length);
                            ds.receive(dp);//将发送端发送的数据包接收到接收端的数据包中
                            String ip = dp.getAddress().getHosyAddress();//获取发送端的ip
                            String data = new String(dp.getData(),0,dp.getLength());//获取数据
                            int port = dp.getPort();//获取发送端的端口号
                            sop(ip+":"+data+":"+port);      
                        }
                    }
                    catch(Exception e)
                    {
                        throw new RuntimeException("接收失败");
                    }
                }
            }
        测试类:
    
            class UDPTest
            {
                public static void main(String[] args)
                {
                    DatagramSocket sendSocket = new DatagramSocket();
                    DatagramSocket receSocket = new DatagramSocket(10000);
    
                    new Thread(new UDPSend(sendSocket)).start();
                    new Thread(new UDPRece(receSocket)).start();
                }
            }
    (2)TCP传输
        Socket和ServerSocket
        建立客户端和服务器端
        建立连接后,通过Socket中的IO流进行数据的传输
        关闭socket
        同样,客户端与服务器端是两个独立的应用程序。
        ****Socket
        **构造方法:
        Socket() 
            通过系统默认类型的 SocketImpl 创建未连接套接字
        Socket(InetAddress address, int port) 
            创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
        Socket(String host, int port) 
            创建一个流套接字并将其连接到指定主机上的指定端口号。
        **方法摘要:
        void close() 
            关闭此套接字。
        InetAddress getInetAddress() 
            返回套接字连接的地址。
        InputStream getInputStream() 
            返回此套接字的输入流。
        OutputStream getOutputStream() 
            返回此套接字的输出流。 
        int getPort() 
            返回此套接字连接到的远程端口。
        void shutdownInput() 
            此套接字的输入流置于“流的末尾”。 
        void shutdownOutput() 
            禁用此套接字的输出流。 
        String toString() 
            将此套接字转换为 String。
        ****ServerSocket
        **构造方法:
        ServerSocket() 
            创建非绑定服务器套接字。 
        ServerSocket(int port) 
            创建绑定到特定端口的服务器套接字。
        方法摘要:
        Socket accept() 
            侦听并接受到此套接字的连接。
        void close() 
            关闭此套接字。 
        InetAddress getInetAddress() 
            返回此服务器套接字的本地地址。
        ****TCP传输流程:
        **客户端:
        建立Socket服务,并制定要连接的主机和端口;
        获取Socket流中的输出流OutputStream,将数据写入流中,通过网络发送给服务端;
        获取Socket流中的输出流InputStream,获取服务端的反馈信息;
        关闭资源。
        **服务端:
        建立ServerSocket服务,并监听一个端口;
        通过ServerSocket服务的accept方法,获取Socket服务对象;
        使用客户端对象的读取流获取客户端发送过来的数据;
        通过客户端对象的写入流反馈信息给客户端;
        关闭资源;
        ****代码示例:
        客户端:
    
            class TCPClient
            {
                public static void main(String[] args)
                {
                    Socket s = new Socket("192.168.1.253",10000);
                    OutputStream os = s.getOutputStream();
                    out.write("这是TCP发送的数据".getBytes());
                    s.close();
                }
            }
        服务端:
    
            class TCPServer
            {
                public static void main(String[] args)
                {
                    ServerSocket ss = new ServerSocket(10000);
                    Socket s = ss.accept();
    
                    String ip = s.getInetAddress().getHostAddress();
                    sop(ip);
    
                    InputStream is = s.getInputStream();
                    byte[] buf = new byte[1024];
                    int len = is.read(buf);
                    sop(new String(buf,0,len));
                    s.close();
                    ss.close();
                }
            }
        TCP需求1:客户端给服务端发送数据,服务端接收到后反馈信息给客户端
        客户端:
    
            class TCPClient
            {
                public static void main(String[] args)
                {
                    Socket s = new Socket("192.168.1.253",10000);
                    OutputStream os = s.getOutputStream();
                    out.write("这是TCP发送的数据".getBytes());
    
                    InputStream is = s.getInputStream();
                    byte[] buf = new byte[1024];
                    int len = is.read(buf);
                    sop(new String(buf,0,len));
                    s.close();
                }
            }
            服务端:
            class TCPServer
            {
                public static void main(String[] args)
                {
                    ServerSocket ss = new ServerSocket(10000);
                    Socket s = ss.accept();
    
                    String ip = s.getInetAddress().getHostAddress();
                    sop(ip);
    
                    InputStream is = s.getInputStream();
                    byte[] buf = new byte[1024];
                    int len = is.read(buf);
                    sop(new String(buf,0,len));
    
                    OutputStream os = s.getOutputStream();
                    out.write("这是TCP发送的数据".getBytes());
    
                    s.close();
                    ss.close();
                }
            }
        TCP需求2:建立一个文本转换服务端,客户给服务端发送文本,服务端将数据转换成大写后返回给客户端
              当客户端输入over时,转换结束
        客户端:
    
            class TCPClient
            {
                public static void main(String[] args)
                {
                    Socket s = new Socket("192.168.1.253",10000);
                    BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
                    BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(
                                            s.getOutputStream()));
                    BufferedReader bufIn = new BufferedReader(new InputStreamReader(
                                            s.getInputStream()));
                    String line = null;
                    while((line = bufr.readLine())!=null)
                    {
                        if("over".equals(line))
                            break;
                        bufOut.write(line);
                        bufOut.newLine();
                        bufOut.flush();
                        String retVal = bufIn.readLine();
                        sop("server:"+retVal);
                    }
                    bufr.close();
                    s.close();
                }
            }
        服务端:
    
            class TCPServer
            {
                public static void main(String[] args)
                {
                    ServerSocket ss = new ServerSocket(10000);
                    Socket s = ss.accept();
    
                    String ip = s.getInetAddress().getHostAddress();
                    sop(ip);
    
                    BufferedReader bufIn = new BufferedReader(new InputStreamReader(
                                            s.getInputStream()));
                    BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(
                                            s.getOutputStream()));
    
                    while((line = bufIn.readLine())!=null)
                    {
                        bufOut.write(line.toUpperCase());
                        bufOut.newLine();
                        bufOut.flush();
                    }
                    s.close();
                    ss.close();
                }
            }
        **需求3:拷贝文件
        客户端:
    
            class TCPClient
            {
                public static void main(String[] args)
                {
                    Socket s = new Socket("192.168.1.253",10000);
                    BufferedReader bufr = new BufferedReader(new FileReader("g:\\demo.txt"));
                    PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
                    String line = null;
                    while((line = bufr.readLine())!=null)
                    {
                        pw.println();
                    }
                    s.shutDownOutput();
                    BufferedReader bufIn = new BufferedReader(new InputStreamReader(
                                            s.getInputStream()));
                    String retVal = bufIn.readLine();
                    sop(retVal);
                    bufr.close();
                    s.close();
                }
            }
        服务端:
    
            class TCPServer
            {
                public static void main(String[] args)
                {
                    ServerSocket ss = new ServerSocket(10000);
                    Socket s = ss.accept();
    
                    String ip = s.getInetAddress().getHostAddress();
                    sop(ip);
    
                    BufferedReader bufIn = new BufferedReader(new InputStreamReader(
                                            s.getInputStream()));
                    PrintWriter out = new PrintWriter(new FileWriter"copy.txt",true);
                    String line =null;
                    while((line = bufIn.readLine())!=null)
                    {
                        out.write(line);
                    }
                    PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
                    pw.println("上传成功");
                    out.close();
                    s.close();
                    ss.close();
                }
            }
        需求4:上传图片
        客户端:
    
            class TCPClient
            {
                public static void main(String[] args)
                {
                    Socket s = new Socket("192.168.1.253",10000);
                    FileInputStream fis = new FileInputStream("g:\\1.bmp");
                    OutputStream out = s.getOutputStream();
                    byte[] buf = new byte[1024];
                    int len = 0;
                    while((len = bufr.read())!=-1)
                    {
                        out.write(buf,0,len);
                    }
                    s.shutDownOutput();
    
                    InputStream in = s.getInputStream();
                    byte[] bufIn = new byte[1024];
                    int lenIn = in.read(bufIn);
                    sop(new String(bufIn,0,lenIn);
                    fis.close();
                    s.close();
                }
            }
        服务端:
    
            class TCPServer
            {
                public static void main(String[] args)
                {
                    ServerSocket ss = new ServerSocket(10000);
                    Socket s = ss.accept();
    
                    String ip = s.getInetAddress().getHostAddress();
                    sop(ip);
                    FileOutputStream fos = new FileOutputStream("g:\\copy.bmp");
                    InputStream in = s.getInputStream();
                    byte[] bufIn = new byte[1024];
                    int lenIn = 0;
                    while((lenIn=bufIn.read())!=-1)
                    {
                        fos.write(bufIn,0,lenIn)
                    }
    
                    OutputStream outIn = s.getOutputStream();
                    outIn.write("上传成功".getBytes());
                    fos.close();
                    s.close();
                    ss.close();
                }
            }
        需求5:客户端并发登陆
            客户端通过键盘录入用户名,服务端对这个用户名进行校验
            如果用户存在,在服务端现实xxx已登录,并在客户端现实欢迎xxx
            如果用户不存在,在服务端现实xxx正在尝试登陆,并在客户端现实xxx用户不存在
            最多登陆三次。
        校验端:
    
            class User implements Runnable
            (
                private Socket s;
                public User(){}
                public User(Socket s)
                {
                    this.s=s;
                }
                public void run()
                {
                    try
                    {
                        BufferedReader bufrIn = new BufferedReader(
                                new InputStream(s.getInputStream()))
                        String name = bufrIn.readLine();
                        if(name==null)
                        {
                            sop("用户名为空");
                            break;
                        }
                        BufferedReader bufr = new BufferedReader(
                                new FileReader("user.txt"));
                        PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
                        String line = null;
                        boolean flag = false;
                        while((line = bufr.reanLine())!=null)
                        {
                            if(line.equals(name))
                            {
                                flag = true;
                                break;
                            }
                            if(flag)
                            {
                                sop(name+"已登陆");
                                pw.println("欢迎"+name);
                                break;
                            }
                            else
                            {
                                sop(name+"正尝试登陆");
                                pw.println(name+"用户不存在");
                            }
    
                        }
                        s.close();
                    }
                    catch(Exception e)
                    {
                        throw new RuntimeException("用户校验失败");
                    }
                }
            )
        客户端:
    
            class LoginClient
            {
                public static void main(String[] args)
                {
                    Socket s = new Socket("192.168.1.253",10000);
                    BufferedReader bufr = new BufferedReader(
                                new InputStreamReader(System.in)));
                    PrintWriter out = new PrintWriter(s.getOutputStream(),true);
                    BufferedReader bufIn = new BufferedReader(
                                new InputStreamReader(s.getInputStream()));
                    for(int i=0;i<3;i++)
                    {
                        String line = bufr.readLine();
                        if(line == null)
                        {
                            sop("用户名不能为空!");
                            break;
                        }
                        out.write(line);
                        String retVal = bufIn.readLine();
                        sop(retVal);    
                    }
                    bufr.close();
                    s.close();
                }
            }
        服务端:
    
    class LoginServer
            {
                public static void main(String[] args)
                {
                    ServerSocket ss = new ServerSocket(10000);
                    while(true)
                    {
                        Socket s = ss.accept();
                        new Thread(new User()).start();
                    }
                }
            }
    展开全文
    xingkonglfs 2017-01-23 20:20:16
  • 75、计算机病毒是指:种人为编制的具有自我复制能力通过非授权入侵而隐藏在可执行程序和数据文件中,影响和破坏计算机的安全的程序;复制性,破坏性,隐藏性,传染性; 76、常见的感染病毒特征:计算机启动比...

     

    1、  CAD是指_计算机辅助设计。

    2、  CAM是指_计算机辅助制造

    3、 在计算机工作时,内存用来存储当前正在使用的程序和数据。

    4、机器语言和汇编语言是低级语言。

    5、 CAI是指计算机辅助教学

    6、 关掉电源后,RAM的存储内容会丢失_。

    7、 只读存储器简称ROM

    8、 8位二进制数所表示的最大的无符号十进制整数为255

    9、 电子元件的发展经过了电子管、晶体管、集成电路和大规模集成电路4个阶段。

    10、 计算机病毒一般具有破坏性、传染性、隐蔽性、潜伏性等特点。

    11、 根据规模大小和功能强弱,计算机可分为巨型机、大型机、中型机、小型机和微型机。

    12、 bit的意思是

    展开全文
    tdtdtudou 2019-09-11 11:16:59
  • 区块链知识普及 区块链的基本内容 基本概念 1.什么是区块链 把多笔交易的信息以及表明该区块的信息打包放在一起,经验证后的这个包就是区块。 每个区块里保存了上个区块的hash值,使区块之间产生关系,也...

    区块链知识普及

    区块链的基本内容

    基本概念

    1.什么是区块链
    把多笔交易的信息以及表明该区块的信息打包放在一起,经验证后的这个包就是区块。
    每个区块里保存了上一个区块的hash值,使区块之间产生关系,也就是说的链了。合起来就叫区块链。

    2.什么是比特币
    比特币概念是2009年中本聪提出的,总量是2100万个。比特币链大约每10分钟产生一个区块,这个区块是矿工挖了10分钟挖出来的。作为给矿工奖励,一定数量的比特币会发给矿工们,但是这个一定数量是每四年减半一次。现在是12.5个。照这样下去2040年全部的比特币问世。
    3.什么是以太坊
    以太坊与比特币最大的区别是有了智能合约。使得开发者在上边可以开发,运行各种应用。

    区块链的特点

    4.分布式账本
    它是一种在网络成员之间共享,复制和同步的数据库。直白说,在区块链上的所有用户都有记账功能,而且内容一致,这样保证了数据不可篡改性。
    5.什么是准匿名性
    相信大家都有钱包,发送交易都用的钱包地址(一串字符串)这就是准匿名。
    6.什么是开放透明性/可追溯
    区块链存储了从历史到现在的所有数据,任何人都可以查看,而且还可以查看到历史上的任何数据。
    7.什么是不可篡改
    历史数据和当前交易的数据不可篡改。数据被存在链上的区块上,有一个hash值,如果修改该区块信息,那么它的hash值也变了,它后边的所有区块的hash值也必须修改,使成为新的链。同时主链还在进行交易产生区块。修改后链也必须一直和主链同步产生区块,保证链的长度一样。代价太大了,只为修改一条数据。
    8.什么是抗ddos攻击

    ddos:黑客通过控制许多人的电脑或者手机,让他们同时访问一个网站,由于服务器的宽带是有限的,大量流量的涌入可能会使得网站可能无法正常工作,从而遭受损失。
    但区块链是分布式的,不存在一个中心服务器,一个节点出现故障,其他节点不受影响。理论上是超过51%的节点遭受攻击,会出现问题。

    区块链分类

    9.主链的定义

    以比特币为例,某个时间点一个区块让2个矿工同时挖出来,然后接下来最先产生6个区块的链就是主链
    10.单链/多链

    单链指的是一条链上处理所有事物的数据结构。
    多链结构,其核心本质是公有链+N个子链构成。只有一条,子链理论上可以有无数条,每一个子链都可以运行一个或多个DAPP系统

    11.公有链/联盟链/私有链

    公有链:每个人都可以参与到区块链的记账中来,并可以下载完整的交易信息。
    联盟链:只允许联盟成员参与记账和查询
    私有链:写入和查看的权限只掌握在一个组织手里。

    区块链的层级结构

    12.共识层,数据层等

    区块链整体结构有六个:数据层,网络层,共识层,激励层,合约层,应用层。数据层:记录数据的一层,属于底层技术;网络层:构建区块链网络的一种架构,它决定了用户与用户之间通过何种方式组织起来。共识层:提供了一套规则,让大家接收和存储的信息达成一致。激励层:设计激励政策,鼓励用户参与到区块链生态中;合约层:一般指“智能合约”,它是一套可以自动执行,根据自己需求编写的合约体系。应用层:区块链上的应用程序,与手机的app类似。

    区块链的基本技术

    区块

    13.时间戳

    时间戳是指从1970年1月1日0时0分0秒0…到现在的当前时间的总秒数,或者总纳秒数等等很大的数字。
    每个区块生成时都有一个时间戳,表明生成区块的时间。

    14.区块/区块头/区块体

    区块是区块链的基本单元,区块头和区块体是区块链的组成部分。区块头里面包含的信息有上一个区块的hash,本区块的hash,时间戳等等。区块体就是区块里的详细数据。

    15.Merkle树

    Merkle树,也叫二叉树,是存储数据的一种数据结构,最底层是所有区块包含的原始数据,上一层是每个区块的hash值,这一层的hash两两组合产生新的hash值,形成新的一层,然后一层层往上,一直到产生一个hash值。这样的结构可以用于快速比较大量的数据,不需要下载全部的数据就可以快速的查找你想要的最底层的历史数据。

    16.什么是扩容

    比特币的一个区块大小大约是1M左右,可以保存4000笔交易记录。扩容就是想把区块变大,能保存更多的数据。

    数据结构

    17.什么是链

    每个区块都会保存上一个区块的hash,使区块之间产生关系,这个关系就是链。通过这个链把区块交易记录以及状态变化等的数据存储起来。

    18.区块高度

    这个不是距离上说的高度,它指是该区块与所在链上第一个区块之间相差的区块总个数。这个高度说明了就是第几个区块,只是标识作用。

    19.分叉
    同一时间内产生了两个区块(区块里的交易信息是一样的,只是区块的hash值不一样),之后在这两个区块上分叉出来两条链,这两条链接下来谁先生成6个区块,谁就是主链,另外的一条链丢弃。
    20.幽灵协议
    算力高的矿池很容易比算力低的矿机产生区块速度快,导致区块链上大部分区块由这些算力高的矿池产生的。而算力低的矿机产生的区块因为慢,没有存储到链上,这些区块将会作废。
    幽灵协议使得本来应该作废的区块,也可以短暂的留在链上,而且也可以作为工作量证明的一部分。这样一来,小算力的矿工,对主链的贡献比重就增大了,大型矿池就无法独家垄断对新区块的确认。

    21.孤块
    之前说过分叉,孤块就是同一时间产生的区块,有一个形成了链,另一个后边没有形成链。那么这个没形成链的块就叫孤块。
    22.叔块
    上边说的孤块,通过幽灵协议,使它成为工作量证明的一部分,那它就不会被丢弃,会保存在主链上。这个区块就是下一个区块的叔块。
    23.重放攻击
    就是黑客把已经发送给服务器的消息,重新又发了一遍,有时候这样可以骗取服务器的多次响应。
    24.有向无环图
    也叫数据集合DAG(有向非循环图)
    DAG是一种理想的多链数据结构。现在说的区块链大都是单链,也就是一个区块连一个区块,DAG是多个区块相连。好处是可以同时生成好几个区块,于是网络可以同时处理大量交易,吞吐量肯定就上升了。但是缺点很多,目前属于研究阶段。
    这里写图片描述
    以太坊DAG:

    解密算法

    25.什么是加密/非对称性加密
    对称加密是加密算法和解密算法都是同一模式,用秘钥来对信息进行加解密,通常情况下,密钥越长,代表着密文被破解的难度越大。对称加密有一个最大弱点:只有一把密钥保证加密和解密数据,所以甲方必须把密钥告诉乙方,否则乙方无法解密。而保存和传递密钥,就成了最头疼的问题。
    非对称加密需要两个(一对)密钥:公开密钥(publickey)和私有密钥(privatekey),用公钥对数据进行加密后,只有对应的私钥才能解密;反之如果私钥用于加密,则只有对应的公钥才能解密。这样可以保证通信双方是通信数据是私密的,并且能确认双方的身份。
    发送的信息以密文的形式传递,接受者能解密的是对称加密,不能解密的是非对称。
    这里写图片描述

    26.数字签名/数字身份/数字证书
    私钥签名,公钥验签。私钥里对应两个大数r,s,把公布的消息和r,s打包成一条信息和公钥一起发送,接收者通过公钥解码信息,产生r,s,对比公钥里的r,s是否一样,一样表示验签成功。

    数字身份就是你在网络上的身份,往往对应着一个唯一的身份识别号,你在网络上进行的操作,都是通过数字身份进行的。

    数字证书:由一些认证机构颁发,证明某个数字签名背后对应的现实中的真实身份。
    27.RSA/椭圆加密算法
    这两种加密方式目前最安全。RSA的密钥长度一般是1024位,而椭圆加密密钥长度是256位。这是主要区别。
    28.环签名
    当我与别人进行交易的时候,我会把自己的私钥和别人的公钥混合在一起,通过某种方式处理成一个签名。由于这是一个多人签名,而且人与人之间无序,这就叫环签名。这样一来,别人只知道这群人参加了交易,单不知道其中谁在交易。
    29.混币服务
    一般来说,区块链上每笔交易信息都会显示一个输入地址和一个输出地址,这样交易双方的地址就暴漏了。而混沌服务就可以把好几笔金额相同的交易在一起进行记录,其中多人输入地址混合,多个输出地址混合。这样就消除了交易双方的对应关系。
    30.零知识证明
    咱们群里有讲过这个知识点。这个方法运用在区块链上,就能完全不透露双方交易信息,但是能达成交易。

    分布式技术

    31.节点/全节点/
    节点:分布式系统中网络的节点,一般指通过网络连接的服务器,计算机等等,你的电脑也可以作为网络上的一个节点。
    全节点:是指拥有该网络上的全部数据的节点。全节点需要同步所有的区块链数据,它能够独立验证区块链上的所有交易并实时更新数据。主要负责给给其他节点发送交易信息,这样其他节点就可以不需要下载全部数据也可以快速验证交易了。
    32.点对点通讯
    两节点直接进行通讯,不需要经过第三方,保证用户的隐私。
    33.分布式存储
    传统的分布式存储是一个中心化的机构在很多地方做了数据备份,在区块链里是通过链中的各个节点开放的存储空间来建立整个区块链里的分布式数据库。区块链里的分布式存储可以提高网络的运行效率,解决了传统分布式存储中服务器处理速度的瓶颈。

    共识机制

    34.什么是共识机制
    在区块链里大家都认同的一个规则。所有的交易或者其他信息传递需要经过共识机制的确认,确认合法后才能将这个信息保存在链上。这样就保证了大家记账的一致性和准确性。
    35.pow
    英文是proof of work,也就是工作量证明。比特币挖矿就是pow机制。原理就是计算机通过变量(nonce)变化,不断的计算产生一串串数字,直到某一串数字满足指定的难度要求,才停止这次计算。这串数字就是新生成区块的hash值。
    36.pos
    英文是proof of stake,也就是权益证明机制。pos机制直接与持有的代币数量和持币时间挂钩。这种模式会根据你持有数字货币的量和时间,分配给你相应的利息。
    简单来说,就是一个根据你持有货币的量和时间,给你发利息的一个制度,在股权证明POS模式下,有一个名词叫币龄,每个币每天产生1币龄,比如你持有100个币,总共持有了30天,那么,此时你的币龄就为3000,这个时候,如果你发现了一个POS区块,你的币龄就会被清空为0。你每被清空365币龄,你将会从区块中获得0.05个币的利息(假定利息可理解为年利率5%),那么在这个案例中,利息 = 3000 * 5% / 365 = 0.41个币,这下就很有意思了,持币有利息。
    37.DPOS
    英文是delegated proof of stake,委托股权证明。
    DPoS给出一种思路,将成千上万个PoS节点,通过某种机制(例如持有代币的数量)选举出若干(奇数个)节点,在这几个节点之间进行投票选举(在一些实现中甚至会在这些节点间以令牌环的方式进行轮询,进一步减少投票开销)出每次的检查点(出块)节点,而不用在网络中全部节点之间进行选择。

    这种机制能够大幅度提升选举效率。在几十个最多上百节点之间进行一致性投票一般来说可以在秒级完成并达到共识,因此DPoS机制可以将检查点(事务确认时间)提升到秒级,通过减少投票节点的数量或采用令牌环机制甚至可以降低到毫秒级。
    这里写图片描述

    这里写图片描述

    38.51%攻击
    一个人控制了全网50%以上的算力,那么他会在竞争下一个区块的记账权的时候更有优势,会比其他人更快的完成工作量证明。这个人因为拥有这么大的算力,很容易会做到1、修改自己的交易记录,这可以使他进行双重支付2、阻止区块确认部分或者全部交易3、阻止部分或全部矿工开采到任何有效的区块

    39.拜占庭将军问题
    描述10个小国攻打一个大国的故事。这个网上可以搜到,不赘述。
    40.改进型实用拜占庭容错PBFT

    我们重点讨论预准备(pre-prepare)、准备(prepare)和确认(commit)这三个历史性阶段。预准备和准备两个阶段用来确保同一个视图中请求发送的时序性(即使对请求进行排序的主节点失效了),准备和确认两个阶段用来确保在不同的视图之间的确认请求是严格排序的。

    预准备阶段
    在预准备阶段,主节点分配一个序列号n给收到的请求,然后向所有备份节点群发预准备消息,预准备消息的格式为<

    区块链扩展技术

    44.分片
    分片技术可以解决交易速度慢和吞吐量低的问题。它将网络上的交易分割成许多小片,不同节点负责验证不同小片的内容。这样节点越多,能同时验证的交易数量也越多,验证的总体速度就加快了。有了分片技术,不同的交易由不同的节点验证。区块链就像一个高度公路上只有一个收费站,因此容易拥堵;分片技术就像提供了好几个收费站,使车辆能迅速通过。但是缺点致命,可能会内容冲突的交易因为不同节点验证通过,而造成双重支付。目前处在研究。
    45.闪电网络/雷电网络
    闪电网络就是让小额交易放在单独一条链上,关键交易放在主链上,这样来提高交易的吞吐量。它采用了一种时间锁技术,如果一方想要篡改交易,另一方在一段时间内发现了这种作弊行为,则可以惩罚对方,同时保护自己的财产不受损失。处于适用阶段。

    基于闪电网络的思路,以太坊社区也提出了自己的链下微支付通道解决方案,它通过智能合约来实现,这就是雷电网络。
    46.跨链和互联链
    kua lian也就是银行的跨行转账类似。典型的跨链技术:公证人机制,侧链,哈希锁定模式。
    互联链是指各种不同的区块链通过某种技术连接起来。
    47.侧链
    侧链是一种连通两种不同的区块链的机制。通过侧链,我们可以把代币转到其他区块链项目上,还可以安全的返回。实现方法:通过侧链技术,我们可以锁定一笔比特币;而另一个区块链项目收到锁定信息后,将允许用户使用一笔与比特币价值相等的的代币。在这期间,各种交易/操作完成后,剩余的代币将被重新锁定,而一个处理消息将会发回比特币主链,使剩余的比特币得到解锁。侧链是一个独立的、隔离的系统,侧链中出现的问题只会影响侧链本身,这极大地降低了创新风险和成本。
    48.图灵完备
    如果一个系统可以编写一个找到答案的程序,这个系统就是图灵完备的。具体实现就是靠智能合约,用户可以根据自己的需求来编写代码,实现所需的功能,调用即可。
    49.去中心化应用DApp
    dapp一般具有开源、自治的特点。app中的数据经过加密后存储在区块链上,保证安全,保护隐私,同时可以改变生产关系。比如在dapp中,用户访问流量所产生的收益,可以直接输出给内容生产者,而不需要经过平台的抽成。

    区块链技术应用

    数字货币

    50.数字货币
    是基于网络算法产生的数字,这些数字被认可的群体被赋予发币的功能,进行交易。

    智能合约

    51.智能合约
    我有讲过。就是在源码中留一块地放智能合约代码。合约里的方法可以被外部调用。
    52.EVM
    以太坊虚拟机的简称。它是建立在以太坊区块链上的一个代码运行环境,用来执行以太坊的智能合约。以太坊是一个点对点的网络,每个节点都会运行着以太坊虚拟机。当一个智能合约生效的时候,节点上的虚拟机就编译代码,执行合约,存储数据,确保执行结果记录在区块链上。
    53.token
    一般认为是代币。严格来讲应该叫数字权益证明。

    数字货币的交易

    账户相关

    54.公钥/私钥
    这属于加密范畴。
    安全散列算法(Secure Hash Algorithm,缩写为SHA),用改算法给任意长度的数据能计算出长度固定的字符串(又称消息摘要),并且该字符串是唯一的。

    比特币私钥其实是使用SHA-256生成的32字节(256位)的随机数,有效私钥的范围则取决于比特币使用的secp256k1 椭圆曲线数字签名标准。大小介于0x1 到0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140之间的数几乎都是合法的私钥。

    在私钥的前面加上版本号,后面添加压缩标志和附加校验码,(所谓附加校验码,就是对私钥经过2次SHA-256运算,取两次哈希结果的前四字节),然后再对其进行Base58编码,就可以得到我们常见的WIF(Wallet import Format)格式的私钥。

    私钥经过椭圆曲线乘法运算,可以得到公钥。公钥是椭圆曲线上的点,并具有x和y坐标。公钥有两种形式:压缩的与非压缩的。早期比特币均使用非压缩公钥,现在大部分客户端默认使用压缩公钥。

    由于数学原理,从私钥推算公钥是可行的,从公钥逆推私钥是不可能的。

    初识比特币的人常有一种误解,认为比特币公钥就是地址,这是不正确的。从公钥到地址还要经过一些运算。

    55.地址
    椭圆曲线算法生成的公钥信息比较长,压缩格式的有33字节,非压缩的则有65字节。地址是为了减少接收方所需标识的字节数。比特币地址的生成步骤如下:

      1 生成私钥与公钥
    
      2 将公钥通过SHA256哈希算法处理得到32字节的哈希值
    
       3 对得到的哈希值通过RIPEMD-160算法来得到20字节的哈希值 —— Hash160
    
       4 把版本号+Hash160组成的21字节数组进行双次SHA256哈希运算,得到的哈希值的头4个字节作为校验和,放置21字节数组的末尾。
    
        5 对组成25位数组进行Base58编码,就得到地址。
    

    具体的过程我们不需要去一一弄清楚,这里面涉及到很的数学公式和原理。但是我们需要从中理解到下面的几点:

      由于椭圆曲线乘法以及哈希函数的特性,我们可以从私钥推导出公钥,也可以从公钥推导出地址,而这个过程是不可逆的。也正因如此,在整个比特币(以太坊等)系统中,公钥是可以公开的,也就是说钱包地址是安全的。私钥是最关键的部分。
    

    挖矿相关

    56.挖矿/矿工/区块奖励
    这里写图片描述

    挖矿过程就是对以上这六个字段进行一系列的转换、连接和哈希运算,并随着不断一个一个试要寻找的随机数,最后成功找到一个随机数满足条件:经过哈希运算后的值,比预设难度值的哈希值小,那么,就挖矿成功了,节点可以向邻近节点进行广播该区块,邻近节点收到该区块,对以上六个字段进行同样的运算,验证合规,再向其它结点转播,其它结点也用同样的算法进行验证,如果全网有51%的结点都验证成功,这个区块就算真正地“挖矿”成功了,每个结点都把这个区块加在上一个区块的后面,并把区块中与自己记录相同的列表删除,再次复生上述过程。

    另外要说的是,不管挖矿成不成功,每个节点都预先把奖励的比特币50个、所有交易的手续费(总输入-总输出)记在交易列表的第一项了(这是“挖矿”最根本的目的,也是保证区块链能长期稳定运行的根本原因),输出地址就是本结点的地址,但如果挖矿不成功,这笔交易就作废了,没有任何奖励。而且这笔叫作“生产交易”的交易不参与“挖矿”计算。
    57.矿机/矿场
    矿机就是各种配置的计算机,算力是他们的最大差距。
    矿机集中在一个地的地方就是矿场
    58.矿池
    就是矿工们联合起来一起组成一个团队,这个团队下的计算机群就是矿池。挖矿奖励,是根据自己的算力贡献度分发。

    59.挖矿难度和算力
    挖矿难度是为了保证产生区块的间隔时间稳定在某个时间短内,如比特币10分钟出块1个。
    算力就是矿机的配置

    交易相关

    60.验证
    当区块链里的验证是对交易合法性的一种确认,交易消息在节点之间传播时,每个节点都会验证一次这笔交易是否合法。比如验证交易的语法是否正确,交易的金额是否大于0,输入的交易金额是否合理,等等。验证通过后打包,交给矿工挖矿。
    61.交易广播
    就是该节点给其他节点通过网络发送信息。
    62.矿工费
    区块链要像永动机一样不停的工作,需要矿工一直维护着这个系统。所以要给矿工们好处费,才能持久。
    63.交易确认
    当交易发生时,记录该笔交易的区块将进行第一次确认,并在该区块之后的链上的每一个区块进行再次确认;当确认数达到6个及以上时,通常认为这笔交易比较安全并难以篡改。
    64.双重交易
    就是我有10块钱,我用这10块钱买了一包烟,然后瞬间操作用这还没到付的10块钱又买了杯咖啡。
    所以验证交易的时候,要确认这10块钱是否已花费。
    65.UTXO未花费的交易输出
    它是一个包含交易数据和执行代码的数据结构,可以理解为存在但尚未消费的数字货币。
    66.每秒交易数量TPS
    也就是吞吐量,tps指系统每秒能处理的交易数量。

    钱包相关

    67.钱包
    与支付宝类似,用来存储数字货币的,用区块链技术更加安全。
    68.冷钱包/热钱包
    冷钱包就是离线钱包,原理是储存在本地,运用二维码通信让私钥永不触网。
    热钱包就是在线钱包,原理是将私钥加密后存储在服务器上,当需要使用时再从服务器上下载下来,并在浏览器端进行解密。
    69.软件钱包/硬件钱包
    软件钱包是一种计算机程序。一般而言,软件钱包是与区块链交互的程序,可以让用户接收、存储和发送数字货币,可以存储多个密钥。
    硬件钱包是专门处理数字货币的智能设备。

    发行相关

    70.空投
    项目方把数字货币发送给各个用户钱包地址。白给
    71.映射
    映射跟区块链货币的发行相关,是链与链之间的映射。比如有一些区块链公司,前期没有完成链的开发,它就依托于以太坊发行自己的货币,前期货币的发行、交易等都在以太坊上进行操作。随着公司的发展,公司自己的链开发完成了,公司想要把之前在以太坊上的信息全部对应到自己的链上,这个过程就是映射。
    72.GOHOST:GHOST协议就是让我们必须选择一个在其上完成计算最多的路径。一个方法确定路径就是使用最近一个区块(叶子区块)的区块号,区块号代表着当前路径上总的区块数(不包含创世纪区块)。区块号越大,路径就会越长,就说明越多的挖矿算力被消耗在此路径上以达到叶子区块。使用这种推理就可以允许我们赞同当前状态的权威版本。

    展开全文
    SmarterEric 2018-07-28 12:37:19
  • qq_41893334 2020-05-31 15:17:30
  • u012087859 2020-09-01 10:21:44
  • qq_36388776 2018-08-30 11:23:12
  • qq_36894974 2020-01-15 11:51:39
  • Kinb_huangwei 2007-12-07 11:45:00
  • Thorne_lu 2020-07-22 16:37:39
  • hao19980724 2018-11-06 19:05:48
  • m0_51538362 2021-03-20 19:34:47
  • m0_37631322 2018-11-07 10:01:34
  • wteruiycbqqvwt 2019-07-10 15:01:37
  • weixin_37981095 2018-06-08 22:48:22
  • qq_24347541 2020-04-13 15:13:39
  • qq_43268396 2020-01-20 00:34:56
  • qq_33682763 2019-06-19 19:24:51
  • wohiusdashi 2018-07-06 17:26:40
  • weixin_45783793 2019-12-25 10:58:40
  • yong325 2013-08-14 10:35:17
  • CSDN_bang 2019-10-16 09:56:00
  • libinemail 2016-03-24 14:23:54
  • kclax 2019-05-25 00:33:55
  • Together_CZ 2017-07-05 21:36:50
  • yumon321 2018-09-21 12:23:28
  • weixin_29139581 2021-06-27 01:12:46
  • liangjunhua5 2017-04-12 23:26:12
  • JankinChan 2020-03-15 17:42:51

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,223
精华内容 3,689
关键字:

安全知识大全100条