目录

  • 一、数据库
  • 1.1 问答题
  • 二、编程方面
  • 1. 填空
  • 2. 编程
  • 三、进阶专业知识
  • 1. web类
  • 四、java语言基础


一、数据库

1.1 问答题

  1. 请说明数据库主键、外键作用,以及建立索引的好处和坏处。
    主键:唯一标识一条记录,不能重复、不允许为空(三泛式中规定)
    作用:约束唯一标识数据库中的每条记录
    个数:每个表应该有一个主键,并且每个表只有一个主键。
    外键:用来指向另一个表中的主键,外键可以重复,可以为空
    作用:用来和另一张表做关联
    个数:每个表中可以包含多个外键
    索引:类比书中的目录,可以包含一个或者多列的值,如果包含多个列,那么顺序也是非常重要的,因为Mysql只能高效的使用最左前缀列。
    创建一个包含两列的索引和创建两个包含一列的索引是大不相同的。
    作用:能用很少的磁盘io快速定位数据
    个数:一个表中可以包含有多个唯一索引
    索引好处:
    1.可以大大加快数据的检索速度。
    2.通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。
    3.可以加快表与表之间的链接。
    4.在使用分组和排序字句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
    坏处:
    1.建立索引,系统要占用大约为表的1.2倍的硬盘和内存控件来保存索引。
    2.更新数据的时候,系统必须要有额外的时间来同时对索引进行更新,以维持数据和索引的一致性。
  2. 请简述什么是事务,事务有哪些属性。
    事务:事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
    事务特性:ACID
    原子性:强调事务的不可分割
    一致性:事务在完成时,必须使所有的数据都保持一致状态
    隔离性:一个事务执行的过程中,不应该受到其他事务的干扰
    持久性:事务一旦执行完成,数据就会持久到数据库中
    如果不考虑隔离性引发的安全性问题:
    脏读:一个事务读到了另一个事务未提交的数据。
    当事务的隔离级别为 READ UNCOMMITED 时,我们在 SESSION 2 中插入的未提交数据在 SESSION 1 中是可以访问的。
    不可重复读:在一个事务中,同一行记录被访问了两次却得到了不同的结果。
    当事务的隔离级别为 READ COMMITED 时,虽然解决了脏读的问题,但是如果在 SESSION 1 先查询了一行数据,在这之后 SESSION 2 中修改了同一行数据并且提交了修改,在这时,如果 SESSION 1 中再次使用相同的查询语句,就会发现两次查询的结果不一样。
    不可重复读的原因就是,在 READ COMMITED 的隔离级别下,存储引擎不会在查询记录时添加行锁,锁定 id = 3 这条记录。
    虚幻度:在一个事务中,同一个范围内的记录被读取时,其他事务向这个范围添加了新的记录。
    重新开启了两个会话 SESSION 1 和 SESSION 2,在 SESSION 1 中我们查询全表的信息,没有得到任何记录;在 SESSION 2 中向表中插入一条数据并提交;由于 REPEATABLE READ 的原因,再次查询全表的数据时,我们获得到的仍然是空集,但是在向表中插入同样的数据却出现了错误。
    这种现象在数据库中就被称作幻读,虽然我们使用查询语句得到了一个空的集合,但是插入数据时却得到了错误,好像之前的查询是幻觉一样。
    RAED UNCOMMITED:使用查询语句不会加锁,可能会读到未提交的行(Dirty Read);
    READ COMMITED:只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果(Non-Repeatable Read);
    REPEATABLE READ:多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读(Phantom Read);
    SERIALIZABLE:InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题;
    MySQL 中默认的事务隔离级别就是 REPEATABLE READ,但是它通过 Next-Key 锁也能够在某种程度上解决幻读的问题。

    事务隔离级别:
    未提交读:脏读、不可重复读、虚幻读都有可能发生
    已提交读:避免脏读,但是不可重复读和虚幻读有可能发生
    可重复读:避免了脏读、不可重复读,但是虚幻读有可能发生
    串行化:避免脏读、不可重复读、虚幻读
    安全:串行化 > 可重复读 》 已提交 》未提交
    性能:串行化 《 可重复读 《 已提交读 《 未提交读
    MySQL默认:可重复读,Oracle默认:已提交读
  3. 如何写出高性能的SQL语句。
  1. 使用“临时表”暂存中间结果
    简化SQL语句的重要方法就是采用临时表暂存中间结果,但是,临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻碍,提高了并发性能。
  2. 统一SQL语句写法
    对于以下两句SQL语句,看似相同,但是数据库查询优化器认为是不同的。
    select * from dual
    select * From dual
    其实就是大小写不同,查询优化器就认为两句是不同的SQL语句,必须进行两次解析,生成两个执行计划。
  3. OLTP系统中SQL语句必须采用绑定变量
    selectfrom orderheader where changetime >‘2010-10-20 00:00:01’
    select
    from orderheader where changetime >‘2010-09-22 00:00:01’
    以上两句语句,查询优化器认为是不同的SQL语句,需要解析两次。如果采用绑定变量
    select*from orderheader where changetime >@chgtime
    @chgtime变量可以传入任何值,这样大量的类似查询可以重用该执行计划了,这可以大大降低数据库解析SQL语句的负担。一次解析,多次重用,是提高数据库效率的原则。
  4. 使用like进行模糊查询时应注意
    select*from contact where username like ‘%yue%’
    关键词%yue%,由于yue前面用到了“%”,因此该查询必然走全表扫描,除非必要,否则不要在关键词前加%

二、编程方面

1. 填空

  1. hello变量的执行结果(hello)
public class Test {
    public static void main(String[] args){
        String hello = new String("hello");
        modify(hello);
        System.out.println(hello); // 输出 hello
    }
    public static void modify(String s){
        s = s + "world!";
    }
}
  1. 写出打印结果(134234)
public class Test {
    public static String output = "";
    public static void foo(int i){
        try{
            if (i == 1){
                throw new Exception();
            }
            output += "1";
        }catch (Exception e){
            output += "2";
        }finally {
            output += "3";
        }

        output += "4";
    }
    public static void main(String[] args){
        foo(0);
        foo(1);
    }
}
  1. 输出“second” 时 x的取值范围(x: 0, -1, -2)
public static void main(String[] args){
    int x = -3;
    if (x > 0){
        System.out.println("first");
    } else if (x > -3){
        System.out.println("second");
    } else {
        System.out.println("third");
    }
}
  1. 分析下面方法,在调用div(1,0)和div(1,1)后分别返回什么(-2 -2)
public static int div(int i, int j){
	try{
		return i / j;
	}catch (Exception e){
		return -1;
	}finally {
		return -2;
	}
}

2. 编程

  1. 使用递归实现阶乘
public static long factorial(int n){
	if (n <= 1){
		return 1;
	}else {
		return n * factorial(n - 1);
	}
}
  1. 单例Singleton的一个实现
// 懒汉式
public class SimpleSingleton {
	private static SimpleSingleton simpleSingleton;
	
	private SimpleSingleton(){}

	public static SimpleSingleton getSimpleSingleton(){
		if (simpleSingleton == null){
			simpleSingleton = new SimpleSIngleton();
		}
		return simpleSingleton;
	}
}

// 饿汉式
public class EagerSingleton {
	private static EagerSingleton eagerSingleton = new EagerSingleton();
	private EagerSingleton(){};
	public static EagerSingleton getSingleton(){
		return eagerSingleton;
	}
}

// 线程安全
public class SafeSingleton {
	private static SafeSingleton safeSingleton;

	private SafeSingleton(){}

	public static synchronized SafeSingleton getSingleton(){
		if (safeSingleton == null){
			safeSingleton  = new SafeSingleton();
		}
		return safeSingleton;
	}
}

// 双检锁
public class DoubleCheckSingleton{
	private volatile static DoubleSingleton doubleSingleton;

	private DoubleSIngleton(){}

	public static synchronized DoubleSingleton getSingleton(){
		if (doubleSingleton == null){
			synchronized(DoubleSingleton.class){
				if (doubleSIngleton == null){
					doubleSingleton = new DoubleSingleton();
				}
			}
		}
		return doubleSingleton;
	}
}

// 静态内部类
public class StaticSIngleton{
	private static class SingletonHolder{
		private static final StaticSingleton INSTANCE = new StaticSingleton();
	}
	
	private StaticSingleton(){}

	public static final StaticSingleton getSingleton(){
		return SingletonHolder.INSTANCE;
	}
}
  1. 实现一个完整的排序算法
public class Sort {
    public static void main(String[] args){
        int[] array = {1, 3, 2, 5, 6, 7};
        bubbleSort(array);
    }
    /**
     * 原理:每次比较两个相邻的元素,将较大的元素交换至右端。
     *
     * 思路:每次冒泡排序操作都会将相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足,就交换这两个相邻元素的次序,一次冒泡至少让一个元素移动到它应该排列的位置,重复N次,就完成了冒泡排序。
     * @param array
     */
    private static void bubbleSort(int[] array){
        for (int i=0; i < array.length - 1; i++){
            for (int j=0; j < array.length - i -1; j++){
                if (array[j] < array[j + 1]){
                    int temp = array[j + 1];
                    array[j + 1] = array[j];
                    array[j] = temp;
                }
            }
        }
    }
}

三、进阶专业知识

1. web类

  1. 在浏览器中输入http://www.baidu.com然后到页面完全显示出来,这期间都发生了哪些事?
  1. 浏览器获取输入的域名www.baidu.com
  2. 浏览器向DNS请求解析www.baidu.com的IP地址
  3. 域名系统DNS解析出百度服务器的地址
  4. 浏览器与该服务器建立TCP链接(默认端口80)
  5. 浏览器发出HTTP请求,请求百度首页
  6. 服务器通过HTTP响应把首页文件发送给浏览器
  7. TCP连接释放
  8. 浏览器将首页文件进行解析,并将web页显示给用户
    涉及到的协议:
    1. 应用层:HTTP(WWW访问协议),DNS(域名解析服务)
    2. 传输层:TCP(为http提供可靠的数据传输), UDP(DNS使用UDP传输)
    3. 网络层:IP(IP数据包传输和路由选择),ICMP(提供网络传输过程中的差错检测),ARP(将本机的默认网关IP地址映射成物理MAC地址)

参考 1 :
参考 2:https://www.nowcoder.com/questionTerminal/f09d6db0077d4731ac5b34607d4431ee

  1. 至少写出3个HTTP状态码及其作用。
    100 - 199 用于指定客户端相应的动作
    200 - 299 用于表示成功
    300 - 399 用于已经转移的文件并且常被包含在定位头信息中指定新的地址信息
    400 - 499 用于指出客户端的错误
    500 - 599 用于表示服务器错误
    成功2xx 成功处理了请求的状态码
    200 服务器已功处理了请求,并返回了请求的内容。
    204 服务器成功处理了请求,但没有返回人户内容。
    重定向3xx 每次请求中使用重定向不要超过5次
    301 请求的网页已永久移动到新位置,
    302 请求的网页临时移动到新位置。
    304 如果网页自请求者上次请求后没有更新,则用304代码告诉搜索引擎机器人,可节省宽带和开销
    客户端错误4xx 表示请求可能出错,妨碍了服务器的处理
    400 服务器不理解请求的语法
    403 服务器拒绝请求
    404 服务器找不到请求的网页
    410 请求的资源永久删除后,服务器返回此响应。该代码与404(未找到)相似,但在资源以前存在而现在不存在的情况下,有时用404代码代替。如果资源已经永久删除,应当使用301指定新的资源的新位置。
    服务器错误5xx 表示服务器在处理请求时发生了内部错误
    500 服务器遇到错误,无法完成请求
    502 服务器暂时不可用,有时是为了防止发生系统过载
    503 服务器目前无法使用(由于超载或者停机维护),通常这只是暂时状态
    505 服务器不支持或拒绝支请求头中指定的HTTP版本
  2. HTTP协议中常见的get和post方法以及你所知道的method区别
    1.GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456.(注意对于用户登录来说,get是不安全的,网页直接显示你的用户名和密码);POST方法是把提交的数据放在HTTP包的Body中。
    GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有大小限制
    GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。
    GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码.
    HTTP规范定义了8种可能的请求方法:
    GET 获取URI中标识资源的一个简单请求
    POST 服务器接受被写入客户端输出流中的数据请求
    HEAD 与GET方法相同,服务器只返回状态行和头标,并不返回请求文档
    PUT 服务器保存请求数据作为指定URI新内容的请求
    DELETE 服务器删除URI中命名的资源请求
    OPTIONS 关于服务器支持的请求方法信息的请求
    TRACE web服务器反馈http请求和其头标的请求
    CONNECT 已文档化但当前未实现的一个方法,预留做隧道处理
  3. 说说https协议是怎么保证传输过程中的安全
  1. Https与Http在传输过程中的差别
    Https与Http都是OSI模型中的传输协议,而唯一不同的就是Https中在Http的应用层和TCP/IP增加了一个SSL/TLS层,其实也是属于应用层,主要用来对数据进行加解密,保证数据的传输的正确性。
  2. Http为何不安全
    http协议属于明文传输协议,交互过程以及数据传输都没有进行加密,通行双方也没有进行任何认证,通信过程非常容易遭遇劫持、监听、篡改,严重情况下,会造成恶意的流量劫持等问题,甚至造成个人隐私泄露等严重安全问题。比如常见的,在http通信过程中,“中间人”将广告链接嵌入到服务器发给用户的http报文里,导致 用户界面出现很多不良链接; 或者是修改用户的请求头URL,导致用户的请求被劫持到另外一个网站,用户的请求永远到不了真正的服务器。这些都会导致用户得不到正确的服务,甚至是损失惨重。
  3. https如何保证数据的安全性
    HTTPS要使客户端与服务器端的通信过程得到安全保证,必须使用对称加密算法,但是协商对称加密算法的过程,需要使用非对称加密算法来保证安全,然而直接使用非对称加密的过程本身也不安全,会有中间人篡改公钥的可能性,所以客户端与服务端不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程本身的安全。这样通过这些机制协商出来一个对称加密算法,就此双方使用该算法进行加密解密。从而解决了客户端与服务器端之间的通信安全问题。

参考:

  1. MD5是不是一个加密算法?为什么?一般用它来做什么?
    MD5 message-digest algorithm 信息-摘要算法。
    MD5其实就是一种算法,可以将一个字符串、文件、压缩包、执行md5后,就可以生成一个固定长度的128bit的串,这个串基本上是唯一的。
    假如有人修改过压缩包,就会生成新的串,这时就可以拿网站提供的串和新生成的串去对比,如果不同,那就证明被修改了。
  1. 加密和摘要是不一样的
    加密后的消息是完整的,具有解密算法,可以得到原始数据。
    摘要得到的消息不是完整的,通过摘要的数据,不能得到原始数据。
  2. md5长度
    md5的长度,默认128bit,也就是128位0或1的二进制串。
    将二进制转换成16进制后,128位也就成了32位。
  3. 为什么网上的还有md5是16位的
    原因是它省去了前8位和后8位,只保留了中间的16位。
  4. md5的作用
    一致性检验、数字签名、安全访问认证(用户密码加密)。
  5. md5不能破解码?
    md5是不可逆的,也就是没有对应的算法,从生产的md5值逆向得到原始数据
  6. md5唯一吗
    md5不唯一,一个原始数据,只对应一个md5,但一个md5可对应多个原始数据。
  1. 说出至少5个你常用的linux命令,以及他们的常用参数和使用场景
    cat
  1. 作用:用于连接并显示指定的一个或多个文件的有关信息,他的使用权是所有用户
  2. 主要参数:
    -n:由第一行开始对所有输出的行进行编号
    -b: 和-n相似,只不过对于空白行不编号
    -s:当遇到有连续两行以上的空白行时,就替换成一行的空白行
  3. 使用场景
    1)cat命令一个最简单的用处是显示文本文件的内容。例如,我们想在命令行看一下README文件的内容,可以使用命令: $ cat README
    2)有时需要将几个文件处理成一个文件,并将这种处理的结果保存到一个单独的输出文件。cat命令在其输入上接受一个或多个文件,并将它们作为一个单独的文件打印到它的输出。例如,把README和INSTALL的文件内容加上行号(空白行不加)之后,将内容附加到一个新文本文件File1中:
    $ cat README INSTALL File1
    3)对行进行编号功能有-b(只能对非空白行进行编号)和-n(可以对所有行进行编号)两个参数:
    $ cat -b /etc/named.conf

find

  1. 作用:是在目录中搜索文件,它的使用权限是所有用户。
  2. 主要参数:
    -depth:使用深度级别的查找过程方式,在某层指定目录中优先查找文件内容。
    -maxdepthlevels:表示至多查找到开始目录的第level层子目录。level是一个非负数,如果level是0的话表示仅在当前目录中查找。
    -mindepthlevels:表示至少查找到开始目录的第level层子目录。
    -mount:不在其它文件系统(如Msdos、Vfat等)的目录和文件中查找。
    -version:打印版本。
    [expression]是匹配表达式,是find命令接受的表达式,find命令的所有操作都是针对表达式的。它的参数非常多,这里只介绍一些常用的参数。
    —name:支持统配符*和?。
    -atimen:搜索在过去n天读取过的文件。
    -ctimen:搜索在过去n天修改过的文件。
    -groupgrpoupname:搜索所有组为grpoupname的文件。
    -user用户名:搜索所有文件属主为用户名(ID或名称)的文件。
    -sizen:搜索文件大小是n个block的文件。
    -print:输出搜索结果,并且打印。
  3. 应用技巧
    find命令查找文件的几种方法:
    (1)根据文件名查找
    例如,我们想要查找一个文件名是lilo.conf的文件,可以使用如下命令:
    find / -namelilo.conf
    find命令后的“/”表示搜索整个硬盘。

mkdir

  1. 作用: 是建立名称为dirname的子目录,与MS DOS下的md命令类似,它的使用权限是所有用户。
  2. 主要参数:
    -m,--mode=模式:设定权限<模式>,与chmod类似。
    -p,--parents:需要时创建上层目录;如果目录早已存在,则不当作错误。
    -v,--verbose:每次创建新目录都显示信息。
    --version:显示版本信息后离开。
  3. 应用实例
    在进行目录创建时可以设置目录的权限,此时使用的参数是“-m”。假设要创建的目录名是“tsk”,让所有用户都有rwx(即读、写、执行的权限),那么可以使用以下命令:
    $ mkdir -m777 tsk

file

  1. 作用: file通过探测文件内容判断文件类型,使用权限是所有用户。
  2. 主要参数 :
    -v:在标准输出后显示版本信息,并且退出。
    -z:探测压缩过的文件类型。
    -L:允许符合连接。
    -f name:从文件namefile中读取要分析的文件名列表。

ls

  1. 作用:用于显示目录内容,类似DOS下的dir命令,它的使用权限是所有用户。
  2. 主要参数 :
    -a,--all:不隐藏任何以“.” 字符开始的项目。
    -A,--almost-all:列出除了“ . ”及 “… ”以外的任何项目。
    -f:不进行排序,-aU参数生效,-lst参数失效。
    -i,--inode:列出每个文件的inode号。
    -I,--ignore=样式:不印出任何符合Shell万用字符<样式>的项目。
    -k:即--block-size=1K。
    -l:使用较长格式列出信息。
    -L,--dereference:当显示符号链接的文件信息时,显示符号链接所指示的对象,而并非符号链接本身的信息。
    -m:所有项目以逗号分隔,并填满整行行宽。
    -n,--numeric-uid-gid:类似-l,但列出UID及GID号。
    -N,--literal:列出未经处理的项目名称,例如不特别处理控制字符。
    -p,--file-type:加上文件类型的指示符号(/=@| 其中一个)。
    -Q,--quote-name:将项目名称括上双引号。
    -r,--reverse:依相反次序排列。
    -R,--recursive:同时列出所有子目录层。
    -s,--size:以块大小为序。

mv

  1. 作用:用来为文件或目录改名,或者将文件由一个目录移入另一个目录中,它的使用权限是所有用户。该命令如同DOS命令中的ren和move的组合。
  2. 主要参数 :
    -i:交互方式操作。如果mv操作将导致对已存在的目标文件的覆盖,此时系统询问是否重写,要求用户回答“y”或“n”,这样可以避免误覆盖文件。
    -f:禁止交互操作。mv操作要覆盖某个已有的目标文件时不给任何指示,指定此参数后i参数将不再起作用。

cmp

  1. 作用 cmp(“compare”的缩写)命令用来简要指出两个文件是否存在差异,它的使用权限是所有用户。
  2. 主要参数 :
    -l: 将字节以十进制的方式输出,并方便将两个文件中不同的以八进制的方式输出。

ln

  1. 作用 ln命令用来在文件之间创建链接,它的使用权限是所有用户。、
  2. 主要参数:
    -f:链结时先将源文件删除。
    -d:允许系统管理者硬链结自己的目录。
    -s:进行软链结(SymbolicLink)。
    -b:将在链结时会被覆盖或删除的文件进行备份。
    链接有两种,一种被称为硬链接(HardLink),另一种被称为符号链接(SymbolicLink)。默认情况下,ln命令产生硬链接。
    硬连接指通过索引节点来进行的连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(InodeIndex)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件才会被真正删除。
    与硬连接相对应,Lnux系统中还存在另一种连接,称为符号连接(SymbilcLink),也叫软连接。软链接文件有点类似于Windows的快捷方式。它实际上是特殊文件的一种。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。

diff

  1. 作用:用于两个文件之间的比较,并指出两者的不同,它的使用权限是所有用户。
  2. 主要参数 :
    -a:将所有文件当作文本文件来处理。
    -b:忽略空格造成的不同。
    -B:忽略空行造成的不同。
    -c:使用纲要输出格式。
    -H:利用试探法加速对大文件的搜索。
    -I:忽略大小写的变化。
    -n --rcs:输出RCS格式。
  1. 怎么理解linux中的管道?通过管道能做哪些工作
    管道符,你可以认为它是一根水管,连接 输入端和输出端。
    a | b
    其中,| 就是管道符,将 输入端 a命令产生的数据 传给 输出端的 b命令来处理。
    管道符后的命令会在新建shell中执行,而新建shell必然就涉及资源的占用。
    因此,能在一个命令中完成的,不要用管道符分两个命令处理。比如:
    tail -n 5 file.txt 就不要写成 cat file.txt | tail -n 5
  2. 一个负责处理用户查询交易的系统如果出现Too many open files 可能是由哪些问题造成的
    这个问题经常在Linux上出现,而且常见于高并发访问文件系统、多线程网络连接等场景。之所以出现这个问题,大多数情况是你的程序没有正常关闭一些资源引起的。
    在Linux系统中,目录、字符设备、块设备、套接字、打印机等都被抽象成了文件,即通常所说的“一切皆文件”。程序操作这些文件时,系统就需要记录每个当前访问file的name、location、access authority等相关信息,这样一个实体被称为file entry。这些实体被记录在open files table中,Linux系统配置了open files table中能容纳多少file entry。如果超过这个配置值,则Linux就会拒绝其他文件操作的请求,并抛出Too many open files。
    解决这个问题可以从两方面着手,一是可以修改系统的配置信息;另外一个是从你的程序层面解决。
    修改系统配置
    根据应用权限,又可以分为系统级和用户级。系统级的修改会对所有用户有效,而用户级只限制每个登录用户的可连接文件数。
    系统级
    通过命令cat /proc/sys/fs/file-max或者sysctl -a来查看fs.file-max的配置数量。改动可以分为临时改动和永久改动,临时改动直接可用sysctl -w [变量名]=[值]来解决。例如:synctl -w fs.file-max=4096。永久改动就需要修改/etc/sysctl.conf文件,如果文件中没有fs.file-max属性,则添加。设置完成后,使用sysctl -p来加载系统参数,在不指定文件位置的情况下,默认会从/etc/sysctl.conf文件中加载。
    用户级
    直接通过命令ulimit -n 设置的数量进行配置,例如:ulimit -n 2048。
    修改代码
    上面那种方法在你的代码没有问题的情况下,可能能解决问题。但是如果是你的程序的原因,则很难有效的解决问题。所以出现这个问题,应该首先检查的你程序,是不是打开的文件或socket没有正常关闭。
    lsof命令可以查看你进程打开的文件,进程打开的端口(TCP、UDP)。由于该命令需要访问核心内存和各种文件,所以需要root用户才能执行。lsof -c 进程名列出指定进程所打开的文件或者lsof -p 进程号列出指定进程号所打开的文件。
    例如Go程序中忘记文件流的关闭、解锁一个加锁的资源、关闭数据库链接等。都会造成open too many files的问题,尤其是在高并发的情况下。所以比较好的习惯就是在进行以上资源操作时,紧跟着使用defer语句。它可以用来推迟执行某个语句或函数到任意位置执行return语句之后

四、java语言基础

  1. 简述JDK JRE JVM的关系
  1. JVM:java虚拟机 。
    作用:保证java语言跨平台。
  2. JRE:java运行环境 jre=java虚拟机+核心类库。
    作用:java程序的运行环境。
  3. JDK :java开发工具集。JDK=jre+java开发工具。
    作用:java程序的开发环境。

简言之:使用jdk开发完成的java程序,交给JRE去运行,由JVM来保证跨平台。

  1. java编程中是如何实现内存管理的,在编程过程中应该注意什么
    java是如何管理内存的
    Java的内存管理就是对象的分配和释放问题。(两部分)
    分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。
    释放 :对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作。但同时,它也加重了JVM的工作。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
    优化程序代码:
  1. 不要显式调用System.gc(),即:尽量避免强制系统做垃圾回收。此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数,大大的影响系统性能。
  2. 尽量减少临时对象的使用。临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,也就减少了主GC的机会。
  3. 对象不用时,最好显式置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
  4. 尽量使用StringBuffer,而不用String来累加字符串。由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象。如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为每次“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
  5. 能用基本类型如Int、long,就不用Integer、Long对象。基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本类型变量。
  6. 尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
  7. 分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的,它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。
  8. 谨慎使用集合数据类型,如数组,树,图,链表等数据结构,这些数据结构对GC来说回收更复杂。
  9. 避免显式申请数组空间,不得不显式申请时,尽量准确估计其合理值。
  10. 尽量避免在类的默认构造器中创建、初始化大量的对象,防止在调用其自类的构造器时造成不必要的内存资源浪费
  11. 做远程方法调用类应用开发时,尽量使用瞬间值变量,除非远程调用端需要获取该瞬间值变量的值。
  12. 尽量在合适的场景下使用对象池技术以提高系统性能。
    总体而言,Java虚拟机的内存优化应从两方面着手:Java虚拟机和Java应用程序。前者指根据应用程序的设计,通过虚拟机参数控制虚拟机逻辑内存分区的大小,以使虚拟机的内存与程序对内存的需求相得益彰;后者指优化程序算法,降低GC负担,提高GC回收成功率。
  1. 如何实现全站方XSS攻击功能。
    什么是XSS攻击?
    XSS攻击使用Javascript脚本注入进行攻击
    XSS攻击常出现在提交表单中,如博客的评论区等,还可以根据js获取本地浏览器的cookie信息,根据cookie信息完全可以模拟用户。
    注意:谷歌浏览器 已经防止了XSS攻击,为了演示效果,最好使用火狐浏览器。
    那么该如何防止XSS攻击呢?
    实现思路:
    使用转义解决。将<转义为&lt >转义为&gt
    ①使用过滤器,拦截所有请求,重写request
    ②重写获取值的方法,将特殊代码转换成html
  2. java中如何实现多线程操作,写一个多线程的片段
public class MyThread extends Thread {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  
 
MyThread myThread1 = new MyThread();  
MyThread myThread2 = new MyThread();  
myThread1.start();  
myThread2.start();
  1. 利用java.servlet.http.HttpServlet实现一个简单的文件上传功能
package net.book.util;
 
import org.apache.commons.fileupload.DiskFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
 
/**
 * @Author: JiangYi
 * @Date: 2018/9/16 10:04
 * @Description:
 */
@WebServlet(name = "FileUploadServlet",urlPatterns = "/fileUploadServlet")
public class FileUploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");//设置返回给浏览器的编码,中文不会乱码
        PrintWriter out = response.getWriter();
 
        //设置保存上传文件的目录
        String uploadDir="e:/upfile";
        out.println("上传文件存储目录!"+uploadDir);
        File fUploadDir = new File(uploadDir);
        if(!fUploadDir.exists()){
            if(!fUploadDir.mkdir()){
                out.println("无法创建存储目录"+uploadDir);
                return;
            }
        }
        if(!DiskFileUpload.isMultipartContent(request)){
            out.println("只能处理mutipart/form-data类型的数据");
            return;
        }
        DiskFileUpload diskFileUpload = new DiskFileUpload();
        diskFileUpload.setSizeMax(1024*1024*200);
        diskFileUpload.setSizeThreshold(1024*1024);
        diskFileUpload.setHeaderEncoding("utf-8");
        List <FileItem> fileItems;
        try {
            fileItems=diskFileUpload.parseRequest(request);
        } catch (FileUploadException e) {
            out.println("解析数据时出现如下问题:");
            e.printStackTrace(out);
            return;
        }
       // out.println("fffffffffffffffff");
        //下面通过迭代器逐个将集合中的文件取出,保存到服务器上
        Iterator it = fileItems.iterator();
        while (it.hasNext()){
            FileItem item = (FileItem) it.next();//由迭代器取出文件项
            if(!item.isFormField()){//忽略那些不属于文件域的表单信息
                String pathSrc = item.getName();
                if(pathSrc.trim().equals(" ")) continue;
                int start = pathSrc.lastIndexOf("\\");//确定最后\的位置以此来获取不含路劲的文件名
                String fileName = pathSrc.substring(start + 1);
                File pathDest = new File(uploadDir, fileName);
                try {
                    item.write(pathDest);
                } catch (Exception e) {
                    out.println("存储文件时遇到如下的问题:");
                    e.printStackTrace(out);
                }
                finally {
                    item.delete();
                }
 
            }
        }
        response.sendRedirect("bookstory/fileUpload_list.jsp");
 
 
    }
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
    }}
  1. 实现一个HashMap
public class MyHashMap {

        //默认初始化大小 16
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
        //默认负载因子 0.75
        private static final float DEFAULT_LOAD_FACTOR = 0.75f;

        //临界值
        private int threshold;

        //元素个数
        private int size;

        //扩容次数
        private int resize;

        private HashEntry[] table;

        public MyHashMap() {
            table = new HashEntry[DEFAULT_INITIAL_CAPACITY];
            threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
            size = 0;
        }

        private int index(Object key) {
            //根据key的hashcode和table长度取模计算key在table中的位置
            return key.hashCode() % table.length;
        }

        public void put(Object key, Object value) {
            //key为null时需要特殊处理,为简化实现忽略null值
            if (key == null) return;
            int index = index(key);

            //遍历index位置的entry,若找到重复key则更新对应entry的值,然后返回
            HashEntry entry = table[index];
            while (entry != null) {
                if (entry.getKey().hashCode() == key.hashCode() && (entry.getKey() == key || entry.getKey().equals(key))) {
                    entry.setValue(value);
                    return;
                }
                entry = entry.getNext();
            }
            //若index位置没有entry或者未找到重复的key,则将新key添加到table的index位置
            add(index, key, value);
        }

        private void add(int index, Object key, Object value) {
            //将新的entry放到table的index位置第一个,若原来有值则以链表形式存放
            HashEntry entry = new HashEntry(key, value, table[index]);
            table[index] = entry;
            //判断size是否达到临界值,若已达到则进行扩容,将table的capacicy翻倍
            if (size++ >= threshold) {
                resize(table.length * 2);
            }
        }

        private void resize(int capacity) {
            if (capacity <= table.length) return;

            HashEntry[] newTable = new HashEntry[capacity];
            //遍历原table,将每个entry都重新计算hash放入newTable中
            for (int i = 0; i < table.length; i++) {
                HashEntry old = table[i];
                while (old != null) {
                    HashEntry next = old.getNext();
                    int index = index(old.getKey());
                    old.setNext(newTable[index]);
                    newTable[index] = old;
                    old = next;
                }
            }
            //用newTable替table
            table = newTable;
            //修改临界值
            threshold = (int) (table.length * DEFAULT_LOAD_FACTOR);
            resize++;
        }

        public Object get(Object key) {
            //这里简化处理,忽略null值
            if (key == null) return null;
            HashEntry entry = getEntry(key);
            return entry == null ? null : entry.getValue();
        }

        public HashEntry getEntry(Object key) {
            HashEntry entry = table[index(key)];
            while (entry != null) {
                if (entry.getKey().hashCode() == key.hashCode() && (entry.getKey() == key || entry.getKey().equals(key))) {
                    return entry;
                }
                entry = entry.getNext();
            }
            return null;
        }

        public void remove(Object key) {
            if (key == null) return;
            int index = index(key);
            HashEntry pre = null;
            HashEntry entry = table[index];
            while (entry != null) {
                if (entry.getKey().hashCode() == key.hashCode() && (entry.getKey() == key || entry.getKey().equals(key))) {
                    if (pre == null) table[index] = entry.getNext();
                    else pre.setNext(entry.getNext());
                    //如果成功找到并删除,修改size
                    size--;
                    return;
                }
                pre = entry;
                entry = entry.getNext();
            }
        }

        public boolean containsKey(Object key) {
            if (key == null) return false;
            return getEntry(key) != null;
        }

        public int size() {
            return this.size;
        }

        public void clear() {
            for (int i = 0; i < table.length; i++) {
                table[i] = null;
            }
            this.size = 0;
        }


        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("size:%s capacity:%s resize:%s\n\n", size, table.length, resize));
            for (HashEntry entry : table) {
                while (entry != null) {
                    sb.append(entry.getKey() + ":" + entry.getValue() + "\n");
                    entry = entry.getNext();
                }
            }
            return sb.toString();
        }
    }

    class HashEntry {
        private final Object key;
        private Object value;
        private HashEntry next;

        public HashEntry(Object key, Object value, HashEntry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public Object getKey() {
            return key;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }

        public HashEntry getNext() {
            return next;
        }

        public void setNext(HashEntry next) {
            this.next = next;
        }
}