java面试题
java面试题
一贱书生 发表于1年前
java面试题
  • 发表于 1年前
  • 阅读 3
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

1. 介绍Java中垃圾回收机制,程序员平时需要关注这个吗?为什么?请举例说明。

Java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。 在程序运行过程中,每创建一个对象都会被分配一定的内存用以存储对象数据。如果只是不停的分配内存,那么程序迟早面临内存不足的问题。所以在任何语言中,都会有一个内存回收机制来释放过期对象的内存,以保证内存能够被重复利用。 
需要注意的是:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身,
System.gc()
Runtime.getRuntime().gc()  
上面的方法调用时用于显式通知JVM可以进行一次垃圾回收,但真正垃圾回收机制具体在什么时间点开始发生动作这同样是不可预料的,这和抢占式的线程在发生作用时的原理一样。

程序员肯定平时需要关注,比如引起内存泄漏, 内存泄漏指由于错误的设计造成程序未能释放已经不再使用的内存,造成资源浪费。GC会自动清理失去引用的对象所占用的内存。但是,由于程序设计错误而导致某些对象始终被引用,那么将会出现内存泄漏。

 

比如下面的例子。使用数组实现了一个栈,有入栈和出栈两个操作。

 

package cglib;

import java.util.EmptyStackException;

public class jiekou {
        
         private Object[] elements;  
    private int Increment = 10;  
    private int size = 0;  
 
    public jiekou(int size) {  
        elements = new Object[size];  
    }  
 
    //入栈  
    public void push(Object o) {  
        capacity();  
        elements[size++] = o;  
    }  
 
    //出栈  
    public Object pop() {  
        if (size == 0)  
            throw new EmptyStackException();  
        return elements[--size];  
    }  
 
    //增加栈的容量  
    private void capacity() {  
        if (elements.length != size)  
            return;  
        Object[] newArray = new Object[elements.length + Increment];  
        System.arraycopy(elements, 0, newArray, 0, size);  
    }  
 
    public static void main(String[] args) {  
        jiekou stack = new jiekou(1000000000);  
        for (int i = 0; i < 1000000000; i++)  
            stack.push(new Integer(i));  
        for (int i = 0; i < 1000000000; i++) {  
            System.out.println(stack.pop().toString());  
        }  
    }  
           
        }

 

  输出:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at cglib.jiekou.<init>(jiekou.java:12)
    at cglib.jiekou.main(jiekou.java:37)

 

这个程序是可用的,支持常用的入栈和出栈操作。但是,有一个问题没有处理好,就是当出栈操作的时候,并没有释放数组中出栈元素的引用,这导致程序将 一直保持对这个Object的引用(此object由数组引用),GC永远认为此对象是可触及的,也就更加谈不上释放其内存了。这就是内存泄漏的一个典型 案例。针对此,修改后的代码为:

 

[java] view plain copy
  1. //出栈  
  2.     public Object pop() {  
  3.         if (size == 0)  
  4.             throw new EmptyStackException();  
  5.         Object o = elements[--size];  
  6.         elements[size] = null;  
  7.         return o;  
  8.     }  

2. 数据库隔离级别介绍、举例说明。

数据库隔离级别有四种,应用《高性能mysql》一书中的说明:

 

 

3. override和overload的区别。

一、重写(override)

override是重写(覆盖)了一个方法,以实现不同的功能。一般是用于子类在继承父类时,重写(重新实现)父类中的方法。

重写(覆盖)的规则:

   1、重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载.

   2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。

   3、重写的方法的返回值必须和被重写的方法的返回一致;

   4、重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类;

   5、被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没s有对其进行重写。

   6、静态方法不能被重写为非静态的方法(会编译出错)。

overload是重载,一般是用于在一个类内实现若干重载的方法,这些方法的名称相同而参数形式不同。
重载的规则:
   1、在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样);
   2、不能通过访问权限、返回类型、抛出的异常进行重载;
   3、方法的异常类型和数目不会对重载造成影响;

多态的概念比较复杂,有多种意义的多态,一个有趣但不严谨的说法是:继承是子类使用父类的方法,而多态则是父类使用子类的方法。
一般,我们使用多态是为了避免在父类里大量重载引起代码臃肿且难于维护。

举个例子:

package cglib;


public class List1
{   
    
    public static void main(String[] args){     
         Triangle tri = new Triangle();     
         System.out.println("三角形Triangle is a type of shape? " + tri.isShape());// 继承     
         List1 shape = new Triangle();     
         System.out.println("My shape形状 has " + shape.getSides() + " sides."); // 多态     
         Rectangle Rec = new Rectangle();     
         List1 shape2 = Rec;     
         System.out.println("My shape has " + shape2.getSides(Rec) + " sides."); //重载     
       }     
       public boolean isShape(){     
         return true;     
       }     
       public int getSides(){     
         return 0 ;     
       }     
       public int getSides(Triangle tri){ //重载     
         return 3 ;     
       }     
       public int getSides(Rectangle rec){ //重载     
        return 4 ;     
       }     
    }

    class Triangle extends List1      
    {     
       public int getSides() { //三角形重写,实现多态     
         return 2; //上面    return 0那段被重写
       }     
    }     
    class Rectangle extends List1      
    {     
       public int getSides(int i) { //长方形重载     
        return i;     
       }     
    }

输出:

三角形Triangle is a type of shape? true
My shape形状 has 2 sides.
My shape has 4 sides.

注意Triangle类的方法是重写,而Rectangle类的方法是重载。对两者比较,可以发现多态对重载的优点:
如果用重载,则在父类里要对应每一个子类都重载一个取得边数的方法;
如果用多态,则父类只提供取得边数的接口,至于取得哪个形状的边数,怎样取得,在子类里各自实现(重写)。

 

4. 求二叉树的最大距离(即相距最远的两个叶子节点),写代码。

5、求二叉树的宽度,先简介思路再写代码。

6、 Hashmap、Hashtable和cocurrentHashMap的区别,要讲出它们各自的实现原理才行,比如Hashmap的扩容机制、cocurrentHashMap的桶分割原理、多线程安全性。

7、进程调度算法,有哪些算法比较难实现?

一、先来先服务和短作业(进程)优先调度算法

1.先来先服务调度算法

先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。当在作业调度中采用该算法时,每次调度都是从 后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。在进程调度中采用FCFS算法时,则 每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。

2.短作业(进程)优先调度算法

短作业(进程)优先调度算法SJ(P)F,是指对短作业或短进程优先调度的算法。它们可以分别用于作业调度和进程调度。 短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。而短进程优先(SPF)调度算法则是从就绪 队列中选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或发生某事件而被阻塞放弃处理机时再重新调度。

二、高优先权优先调度算法

1.优先权调度算法的类型

为了照顾紧迫型作业,使之在进入系统后便获得优先处理,引入了最高优先权优先(FPF)调度算法。此算法常被用于批处理系统中,作为作业调度算法,也作为多种操作系统中的进程调度算法,还可用于实时系统中。当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程,这时,又可进一步把该算法分成如下两种。

1) 非抢占式优先权算法

在这种方式下,系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成;或因发生某事件 使该进程放弃处理机时,系统方可再将处理机重新分配给另一优先权最高的进程。这种调度算法主要用于批处理系统中;也可用于某些对实时性要求不严的实时系统 中。

2) 抢占式优先权调度算法

在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高 的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。因此,在采用这种调度算法时,是每当 系统中出现一个新的就绪进程i 时,就将其优先权Pi与正在执行的进程j 的优先权Pj进行比较。如果Pi≤Pj,原进程Pj便继续执行;但如果是Pi>Pj,则立即停止Pj的执行,做进程切换,使i 进程投入执行。显然,这种抢占式的优先权调度算法能更好地满足紧迫作业的要求,故而常用于要求比较严格的实时系统中,以及对性能要求较高的批处理和分时系 统中。

2.高响应比优先调度算法

在批处理系统中,短作业优先算法是一种比较好的算法,其主要的不足之处是长作业的运行得不到保证。如果我们能为每个作业引入前面所述的动态优先权, 并使作业的优先级随着等待时间的增加而以速率a 提高,则长作业在等待一定的时间后,必然有机会分配到处理机。该优先权的变化规律可描述为:
操作系统
由于等待时间与服务时间之和就是系统对该作业的响应时间,故该优先权又相当于响应比RP。据此,又可表示为:
操作系统
由上式可以看出:

(1) 如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业。

(2) 当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务。

(3) 对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高,从而也可获得处理机。简言之,该算法既照顾了短作 业,又考虑了作业到达的先后次序,不会使长作业长期得不到服务。因此,该算法实现了一种较好的折衷。当然,在利用该算法时,每要进行调度之前,都须先做响 应比的计算,这会增加系统开销。

三、基于时间片的轮转调度算法

1.时间片轮转法

1) 基本原理

在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU 分配给队首进程,并令其执行一个时间片。时间片的大小从几ms 到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机 分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。换言 之,系统能在给定的时间内响应所有用户的请求。

2.多级反馈队列调度算法

前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进 程优先和基于进程长度的抢占式调度算法都将无法使用。而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要, 因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。

(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执 行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……, 第i+1个队列的时间片要比第i个队列的时间片长一倍。

(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如 果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未 完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。

(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为 某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行 的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。

8、 linux下如何修改进程优先级?(nice命令的使用)。

进程的优先级决定了进程是否优先被cpu分配资源进行处理。

在cpu资源十分充足时,每个正在运行的进程都能分配到足够的资源进行处理,此时调整进程的优先级是没有什么意义的;

如果cpu资源紧张时,top查看cpu使用达到90%以上时,优先级高的进程将被优先分配资源去执行。

 

如果此时手头有一个非常紧要的任务要执行,比如传输一个非常重要的数据或是准备给客户发一封非常紧急的邮件,

你希望这些任务优先地被执行完成,那么就需要调整这些任务的优先级了。

优先级的值=优先系数+nice值          

 

优先系数由系统内核决定,不可更改

nice值可以手动更改,范围是 -20~19

优先级的值越低,优先级越高;优先级的值越高,优先级越低。

所以想调整成最高优先级的话,就将nice值设为-20;想调整成最低优先级的话,将nice值设为19。

 

调整优先级:

1、任务未运行前进行调整

# nice -n-20  sh /xxx/xxx.sh          --以最高优先级运行xxx.sh这个脚本

# nice -n19  sh /xxx/xxx.sh           --以最低优先级运行xxx.sh这个脚本

2、任务已经开始运行的情况下调整

# top                                        --查看系统当前进程运行情况

>  r                                         --键入小r

> PID to renice:                             --提示输入运行的进程的pid

> Renice PID 23302 to value:                 --把这个进程的nice值设置为多少,根据需要进行调整

# renice -20 PID                           将进程的nice值改为-20

# renice 19  PID                           将进程的nice值改为19

9、 linux下性能监控命令uptime介绍,平均负载的具体含义是什么?建议看server load概念。

主要是用来查询系统最近一次启动后运行了多长时间

  uptime命令回显中的load average所表示的意思都是表示过去的1分钟、5分钟和15分钟内进程队列中的平均进程数量。

   uptime命令,有两大用处,一个是看您的机器的运行时间,另一个就是看看您的cpu 负载如何?
  uptime
  10:19:04 up 257 days, 18:56, 12 users, load average: 2.10, 2.10,2.09

  1、10:19:04 //系统当前时间
  2、up 257 days, 18:56 //主机已运行时间,时间越大,说明你的机器越稳定。
  3、12 user //用户连接数,是总连接数而不是用户数
  4、load average // 系统平均负载,统计最近1,5,15分钟的系统平均负载

系统平均负载被定义为在特定时间间隔内运行队列中的平均进程数。如果一个进程满足以下条件则其就会位于运行队列中:
  - 它没有在等待I/O操作的结果
  - 它没有主动进入等待状态(也就是没有调用'wait')
  - 没有被停止(例如:等待终止)

一般来说只要每个CPU的当前活动进程数不大于3那么系统的性能就是良好的,如果每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题。对于上 面的例子来说,假设系统有两个CPU,那么其每个CPU的当前任务数为: 2.10 /2=1.05。这表示该系统的性能是可以接受的。

结论

取得CPU核心数目N,观察后面2个数字,用数字/N,如果得到的值小于0.7即可无忧。

 

10、 linux下如何调试程序?说到gdb,具体如何调试?如何查看core文件中的堆栈信息等(bt指令)。

答:http://www.cnblogs.com/AloneSword/p/3512797.html

 

调试的话输入: gdb filename core  

filename就是产生core文件的可执行文件,core就是产生的dump文件

查看栈信息

—————

当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。当你的程序

调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入

“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。

下面是一些查看函数调用栈信息的GDB命令:

backtrace

bt

打印当前的函数调用栈的所有信息。如:

(gdb) bt

#0 func (n=250) at tst.c:6

#1 0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30

#2 0x400409ed in __libc_start_main () from /lib/libc.so.6

从上可以看出函数的调用栈信息:__libc_start_main --> main()--> func()

 

http://blog.chinaunix.net/uid-29867135-id-5019748.html

 

11、打印二叉树两个叶子节点间的路径,写代码

12、 字符串中第一个只出现一次的字符,如何优化算法使得遍历次数更少?

13、socket编程相关,如果服务器这边调用write写了100个字节的数据,客户端想要获得这个数据,是直接用read系统调用,参数也是100吗?

答:http://www.cnblogs.com/leijiangtao/p/4479041.html

http://blog.chinaunix.net/uid-24868917-id-278720.html

http://blog.csdn.net/csu_max/article/details/38623053

14、 新闻缓存预算问题:一般为了追求时间性能,都需要缓存一些新闻数据,你怎么计算所需预算?然后申请需要的主机……

答:http://www.cnblogs.com/futan/archive/2013/04/21/cachehuancun.html

 

15、 多线程的适用场景是什么?为啥要用多线程?

场景一:一个业务逻辑有很多次的循环,每次循环之间没有影响,比如验证1万条url路径是否存在,正常情况要循环1万次,逐个去验证每一条URL,这样效率会很低,假设验证一条需要1分钟,总共就需要1万分钟,有点恐怖。这时可以用多线程,将1万条URL分成50等份,开50个线程,没个线程只需验证200条,这样所有的线程执行完是远小于1万分钟的。

场景二:需要知道一个任务的执行进度,比如我们常看到的进度条,实现方式可以是在任务中加入一个整型属性变量(这样不同方法可以共享),任务执行一定程度就给变量值加1,另外开一个线程按时间间隔不断去访问这个变量,并反馈给用户。
总之使用多线程就是为了充分利用cpu的资源,提高程序执行效率,当你发现一个业务逻辑执行效率特别低,耗时特别长,就可以考虑使用多线程。

--举个简单的例子:
假设有个请求,这个请求服务端的处理需要执行3个很缓慢的IO操作(比如数据库查询或文件查询),那么正常的顺序可能是(括号里面代表执行时间):
a、读取文件1  (10ms)
b、处理1的数据(1ms)
c、读取文件2  (10ms)
d、处理2的数据(1ms)
e、读取文件3  (10ms)
f、处理3的数据(1ms)
g、整合1、2、3的数据结果 (1ms)
单线程总共就需要34ms。
那如果你在这个请求内,把ab、cd、ef分别分给3个线程去做,就只需要12ms了。

所以多线程不是没怎么用,而是,你平常要善于发现一些可优化的点。然后评估方案是否应该使用。
假设还是上面那个相同的问题:但是每个步骤的执行时间不一样了。
a、读取文件1  (1ms)
b、处理1的数据(1ms)
c、读取文件2  (1ms)
d、处理2的数据(1ms)
e、读取文件3  (28ms)
f、处理3的数据(1ms)
g、整合1、2、3的数据结果 (1ms)
单线程总共就需要34ms。
如 果还是按上面的划分方案(上面方案和木桶原理一样,耗时取决于最慢的那个线程的执行速度),在这个例子中是第三个线程,执行29ms。那么最后这个请求耗 时是30ms。比起不用单线程,就节省了4ms。但是有可能线程调度切换也要花费个1、2ms。因此,这个方案显得优势就不明显了,还带来程序复杂度提 升。不太值得。

那么现在优化的点,就不是第一个例子那样的任务分割多线程完成。而是优化文件3的读取速度。
可能是采用缓存和减少一些重复读取。
首 先,假设有一种情况,所有用户都请求这个请求,那其实相当于所有用户都需要读取文件3。那你想想,100个人进行了这个请求,相当于你花在读取这个文件上 的时间就是28×100=2800ms了。那么,如果你把文件缓存起来,那只要第一个用户的请求读取了,第二个用户不需要读取了,从内存取是很快速的,可 能1ms都不到。

伪代码:

1

2

3

4

5

6

7

8

9

10

11

public class MyServlet extends Servlet{

    private static Map<String, String> fileName2Data = new HashMap<String, String>();

    private void processFile3(String fName){

        String data = fileName2Data.get(fName);

        if(data==null){

            data = readFromFile(fName);    //耗时28ms

            fileName2Data.put(fName, data);

        }

        //process with data

    }

}


看起来好像还不错,建立一个文件名和文件数据的映射。如果读取一个map中已经存在的数据,那么就不不用读取文件了。
可是问题在于,Servlet是并发,上面会导致一个很严重的问题,死循环。因为,HashMap在并发修改的时候,可能是导致循环链表的构成!!!(具体你可以自行阅读HashMap源码)如果你没接触过多线程,可能到时候发现服务器没请求也巨卡,也不知道什么情况!
好的,那就用ConcurrentHashMap,正如他的名字一样,他是一个线程安全的HashMap,这样能轻松解决问题。

1

2

3

4

5

6

7

8

9

10

11

public class MyServlet extends Servlet{

    private static ConcurrentHashMap<String, String> fileName2Data = new ConcurrentHashMap<String, String>();

    private void processFile3(String fName){

        String data = fileName2Data.get(fName);

        if(data==null){

            data = readFromFile(fName);    //耗时28ms

            fileName2Data.put(fName, data);

        }

        //process with data

    }

}



这样真的解决问题了吗,这样虽然只要有用户访问过文件a,那另一个用户想访问文件a,也会从fileName2Data中拿数据,然后也不会引起死循环。

可是,如果你觉得这样就已经完了,那你把多线程也想的太简单了,骚年!
你会发现,1000个用户首次访问同一个文件的时候,居然读取了1000次文件(这是最极端的,可能只有几百)。What the fuckin hell!!!

难道代码错了吗,难道我就这样过我的一生!

好好分析下。Servlet是多线程的,那么
 

1

2

3

4

5

6

7

8

9

10

11

12

public class MyServlet extends Servlet{

    private static ConcurrentHashMap<String, String> fileName2Data = new ConcurrentHashMap<String, String>();

    private void processFile3(String fName){

        String data = fileName2Data.get(fName);

        //“偶然”-- 1000个线程同时到这里,同时发现data为null

        if(data==null){

            data = readFromFile(fName);    //耗时28ms

            fileName2Data.put(fName, data);

        }

        //process with data

    }

}


上面注释的“偶然”,这是完全有可能的,因此,这样做还是有问题。

因此,可以自己简单的封装一个任务来处理。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

public class MyServlet extends Servlet{

    private static ConcurrentHashMap<String, FutureTask> fileName2Data = new ConcurrentHashMap<String, FutureTask>();

    private static ExecutorService exec = Executors.newCacheThreadPool();

    private void processFile3(String fName){

        FutureTask data = fileName2Data.get(fName);

        //“偶然”-- 1000个线程同时到这里,同时发现data为null

        if(data==null){

            data = newFutureTask(fName);

            FutureTask old = fileName2Data.putIfAbsent(fName, data);

            if(old==null){

                data = old;

            }else{

                exec.execute(data);

            }

        }

        String d = data.get();

        //process with data

    }

     

    private FutureTask newFutureTask(final String file){

        return  new FutureTask(new Callable<String>(){

            public String call(){

                return readFromFile(file);

            }

 

            private String readFromFile(String file){return "";}

        }

    }

}

一个文本文件有100M,全是字符串,我要执行切分字符串,每达到N长度便执行切腹,最后求切分完成的字符串的集合

单线程处理:读取文本文件数据,扫描全部数据,一个一个的切分,最后消耗时间=文件传输时间(文本数据加载到内存)+切分过程消耗

多线程处理:

专门设置一个线程执行加载数据的操作,此时,如果加载的数据达到一个设定值,启动一个切线程处理,如此继续,多个切分字符串的线程能够并发执行,CPU的利用率提高了(文件传输的过程中没有占用处理器,而可以将加载的部分数据分配给切分线程,占用处理器来执行任务)

总结:

单线程处理,文件加载的过程中,处理器一直空闲,但也被加入到总执行时间之内,串行执行切分总时间,等于每切分一个时间*切分后字符串的个数,执行程序,估计等几分钟能处理完就不错了

多线程处理,文件加载过程与拆分过程,拆分过程与拆分过程,都存在并发——文件加载的过程中就执行了切分任务,切分任务执行过程中多线程并行处理,总消耗时间能比单线程提高很多,甚至几个数量级都不止。

多线程使用的目的:

1、  吞吐量:做WEB,容器帮你做了多线程,但是它只能帮你做请求层面的,简单的说,就是一个请求一个线程(如struts2,是多线程的,每个客户端请求创建一个实例,保证线程安全),或多个请求一个线程,如果是单线程,那只能是处理一个用户的请求

2、  伸缩性:通过增加CPU核数来提升性能。

 多线程的使用场景:

1、  常见的浏览器、Web服务(现在写的web是中间件帮你完成了线程的控制),web处理请求,各种专用服务器(如游戏服务器)

2、  servlet多线程

3、  FTP下载,多线程操作文件

4、  数据库用到的多线程

5、  分布式计算

6、  tomcat,tomcat内部采用多线程,上百个客户端访问同一个WEB应用,tomcat接入后就是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用我们的servlet程序,比如doGet或者dpPost方法

7、  后台任务:如定时向大量(100W以上)的用户发送邮件;定期更新配置文件、任务调度(如quartz),一些监控用于定期信息采集

8、  自动作业处理:比如定期备份日志、定期备份数据库

9、  异步处理:如发微博、记录日志

10、             页面异步处理:比如大批量数据的核对工作(有10万个手机号码,核对哪些是已有用户)

11、             数据库的数据分析(待分析的数据太多),数据迁移

12、             多步骤的任务处理,可根据步骤特征选用不同个数和特征的线程来协作处理,多任务的分割,由一个主线程分割给多个线程完成

16、

分析输入url到页面返回的过程(或者查询返回过程)

1.      我们输入一个域名:www.baidu.com  

2.      浏览器查找浏览器缓存,如果有域名的IP地址则返回,如果没有继续查找;

3.      系统查找系统缓存,如果有域名的IP地址则返回,如果没有继续查找;

4.      路由器查找路由器缓存,如果有域名的IP地址则返回,如果没有继续查找;

5.      本地域名服务器采用迭代查询,它先向一个根域名服务器查询;

6.      根域名服务器告诉本地域名服务器,下一次应查询的顶级域名服务器dns.com的IP地址;

7.      本地域名服务器向顶级域名服务器dns.com进行查询;

8.      顶级域名服务器dns.com告诉本地域名服务器,下一次应查询的权限域名服务器dns.baidu.com的IP地址;

9.      本地域名服务器向权限域名服务器dns.baidu.com进行查询;

10.  权限域名服务器dns.baidu.com告诉本地域名服务器,所查询的主机www.baidu.com的IP地址;

11.  本地域名服务器最后把查询结果告诉主机;

12.  主机浏览器获取到Web服务器的IP地址后,与服务器建立TCP连接;

13.  浏览器所在的客户机向服务器发出连接请求报文;

14.  服务器接收报文后,同意建立连接,向客户机发出确认报文;

15.  客户机接收到确认报文后,再次向服务器发出报文,确认已接收到确认报文;

16.  此处客户机与服务器之间的TCP连接建立完成,开始通信;

17.  浏览器发出取文件命令:GET;

18.  服务器给出响应,将指定文件发送给浏览器;

19.  浏览器释放TCP连接;

20.  浏览器所在主机向服务器发出连接释放报文,然后停止发送数据;

21.  服务器接收到释放报文后发出确认报文,然后将服务器上未传送完的数据发送完;

22.  服务器数据传输完毕后,向客户机发送连接释放报文;

23.  客户机接收到报文后,发出确认,然后等待一段时间后,释放TCP连接;

24.  浏览器显示页面中所有文本。

共有 人打赏支持
粉丝 14
博文 722
码字总数 600072
×
一贱书生
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: