• 美团一面


    目录

    写个sql

    给二叉树 写前序 中序 后序遍历结果

    判断回文

    怼简历

    sring aop ,springboot,springcloud

    jvm 讲讲gc

    讲讲osi七层模型

    tcp udp

    设计模式 懒汉饿汉区别,抽象简单工厂区别

    linux 删除日志文件最后十行

    写个sql注入

    快排时间复杂度 空间复杂度

    如何创建线程

    数据库隔离级别

    如何提高服务器性能


    写个sql

    给二叉树 写前序 中序 后序遍历结果

    判断回文

    判断回文的三种方式:

    第一种方法:就是将这串数组逆序,然后判断逆序后的数字是否和正序后的数字完全一样,如果完全一样就是回文。可以利用栈先进后出的性质实现逆序。

    #include<bits/stdc++.h>
    using namespace std;
    int a[9999];
    int n;
    stack<int> q;//定义一个栈结构让这个数组逆序输出
    class heat{
    public:
      void ruzhan()//入栈操作,利用栈先进后出特性可以使用户给定的一串数字逆序输出
      {
          int i = 0;
          for(i=0;i<n;i++)
            q.push(a[i]);
       }
    public:
      bool hexin()//核心代码,利用回文结构的性质,一串数字(字符串)正序和逆序完全一样
      {
        for(int i=0;i<n;i++){
          if(a[i]!=q.top())
            return false;
          q.pop();
        }
        return true;
      }
    };
    int main()
    {
      cin>>n;
      for(int i=0;i<n;i++)
        cin>>a[i];
      heat p;
      p.ruzhan();
      cout<<p.hexin()<<endl;
    }

    第二种方法:只把数组后半部分利用栈结构进行逆序,然后和前部分进行对比,如果全部相等,就证明是回文数列,否则就不是回文数列。

    #include<bits/stdc++.h>
    using namespace std;
    int a[9999];
    int n;
    int fast=0;
    int slow=0;
    stack<int> q;
    
    class heat{
    public:
      void fastandslow()//这个函数是用来让快慢指针在数组上走动的,最终确定好满指针指向的数组;
      {
          while(fast+2<n){
            slow+=1;
            fast+=2;
          }
       }
    public:
      void ruzhan()//将数组后半部分的数字入栈,目的是让这些数字逆序输出
      {
          for(int i=slow+1;i<n;i++){
            q.push(a[i]);
          }
       }
    public:
      bool hexin()//核心代码:利用回文结构的性质,一串数字(字符串)前半部分数字正序和后半部分逆序完全一样,就说明是回文结构
        {              
            if(slow==n/2)//如果数字总个数是奇数,就执行下面代码
            {
                for(int i=0;i<slow;i++)
                {
                    if(a[i]!=q.top())//正常访问数组是正序,通过栈访问是逆序,只要正序和逆序有一个数字不同,直接返回false
                        return false;
                   q.pop();
                }
                return true;//全部一样  返回true
            }
            else//如果数字个数为奇数,就执行下面代码
            {
                for(int i=0;i<=slow;i++)
                {
                    if(a[i]!=q.top())//正常访问数组是正序,通过栈访问是逆序,只要正序和逆序有一个数字不同,直接返回false
                        return false;
                   q.pop();
                }
                return true;//全部一样  返回true
            }
        }
    };
    int main(){
      cin>>n;
      for(int i=0;i<n;i++)
        cin>>a[i];
      heat p;
      p.fastandslow();
      p.ruzhan();
      cout<<p.hexin()<<endl;
    }

    第三种方法:与第二种方法类似,只不过第三种方法不需要使用栈来实现逆序,直接在数组中实现逆序。

    #include<bits/stdc++.h>
    using namespace std;
    int a[9999];
    int n;
    int fast=0;
    int slow=0;
    void swap(int i,int j){
      int temp=a[i];
      a[i]=a[j];
      a[j]=temp;
    }
    class heat{
    public:
      void fastandslow(){
        while(fast+2<n){
          slow+=1;
          fast+=2;
        }
      }
    public:
      void hounxu(){
        int first=slow+1;
        int end=n-1;
        while(first<end){
          swap(first,end);
          end-=1;
          first+=1;
        }
      }
    public:
      bool hexin(){
        if(slow==n/2){
          for(int i=0,j=slow+1;i<slow;i++,j++)
            if(a[i]!=a[j])
              return false;
            return true;
        }else{
          for(int i=0,j=slow+1;i<=slow;i++,j++)
            if(a[i]!=a[j])
              return false;
            return true;
        }
      }
    }
    int main(){
      cin>>n;
      for(int i=0;i<n;i++)
        cin>>a[i];
      heat p;
      p.fastandslow();
      p.hounxu();
      cout<<p.hexin()<<endl;
    }

    怼简历

    sring aop ,springboot,springcloud

    https://blog.csdn.net/strawqqhat/article/details/88563864

    https://blog.csdn.net/strawqqhat/article/details/88563885

    jvm 讲讲gc

    https://blog.csdn.net/strawqqhat/article/details/83627594

    讲讲osi七层模型

    https://blog.csdn.net/strawqqhat/article/details/88563836

    tcp udp

    设计模式 懒汉饿汉区别,抽象简单工厂区别

    https://blog.csdn.net/strawqqhat/article/details/84680240

    懒汉模式:在类加载的时候不被初始化。

    饿汉模式:在类加载的时候完成了初始化,但是加载过程比较慢,获取对象比较快。

    饿汉模式是线程安全的,在类创建好一个静态对象提供给系统使用,懒汉模式在创建对象时不加入synchronized,会导致 对象的访问是不安全的。

    https://blog.csdn.net/strawqqhat/article/details/88564001

    linux 删除日志文件最后十行

    linux 删除文件最后几行

    [root@server ~]# A=$(sed -n '$=' a.txt)
    [root@server ~]# sed $(($A-3+1)),${A}d a.txt

    或者使用上面两条命令。删除的是倒数3行的。
    如果删除倒数300 ,那就把3改为300 就可以了。

    [root@server ~]# cat aa.txt 
    aaaa
    bbbb
    cccc
    dddd
    eeee
    [root@server ~]# sed '2,$d' -i aa.txt    

    -i 是要在原文件上修改。如果不需要修改,就不用i 了。
    [root@server ~]# cat aa.txt 
    aaaa
    [root@server ~]#

    其中 ,sed '2,$d' -i aa.txt 
    这条命令是 删除从第2行(包括第2行)到文件末尾的所有行。
     

    写个sql注入

    sql注入,一个例子让你知道什么是sql注入

    这篇文章说的非常好

    https://www.cnblogs.com/sdya/p/4568548.html

    我就是按照文中例子,亲自在我之前用final框架做的项目中,操作了一遍,的确是实现了用户登录。

    在不知道用户名和密码的情况下实现了用户登录

    重现sql注入过程如下:

    1、在用户名输入' or 1=1 --,然后随便输入一个密码

    2、点击登录,我竟然登录进去系统了。

    为什么呢?

    debug一下源代码,就不难知道原因。

    点击登录,得到的sql语句是:

    select * from AUTH_USER t where t.LOG_IN_NAME='' or 1=1 --' and  t.PASSWORD='gdd'

    放入oracle去执行:

    返回了所有的用户信息,这样authUser就肯定不是null了,表示数据库有相关用户,当然就算是登录成功了。

    果不其然成功进入了系统界面,这样用户就算是登录了,虽然是一个随机的一个用户,但是该用户的操作他都可以干,甚至如果知道框架的action地址,那么在浏览器中输入功能的action的地址,他想干什么就干什么了。

    快排时间复杂度 空间复杂度

    如何创建线程

    https://blog.csdn.net/strawqqhat/article/details/88564120

    数据库隔离级别

    https://blog.csdn.net/strawqqhat/article/details/88564150

    如何提高服务器性能

    1. 提高CPU并发计算能力

    服务器之所以可以同时处理多个请求,在于操作系统通过多执行流体系设计使得多个任务可以轮流使用系统资源,这些资源包括CPU,内存以及I/O. 这里的I/O主要指磁盘I/O, 和网络I/O。

    多进程 & 多线程

    多执行流的一般实现便是进程,多进程的好处可以对CPU时间的轮流使用,对CPU计算和IO操作重叠利用。这里的IO主要是指磁盘IO和网络IO,相对CPU而言,它们慢的可怜。

    而实际上,大多数进程的时间主要消耗在I/O操作上。现代计算机的DMA技术可以让CPU不参与I/O操作的全过程,比如进程通过系统调用,使得CPU向网卡或者磁盘等I/O设备发出指令,然后进程被挂起,释放出CPU资源,等待I/O设备完成工作后通过中断来通知进程重新就绪。对于单任务而言,CPU大部分时间空闲,这时候多进程的作用尤为重要。

    多进程不仅能够提高CPU的并发度。其优越性还体现在独立的内存地址空间和生命周期所带来的稳定性和健壮性,其中一个进程崩溃不会影响到另一个进程。

    但是进程也有如下缺点:

    1. fork()系统调用开销很大: prefork

    2. 进程间调度和上下文切换成本: 减少进程数量

    3. 庞大的内存重复:共享内存

    4. IPC编程相对比较麻烦

    减少进程切换

    当硬件上下文频繁装入和移出时,所消耗的时间是非常可观的。可用Nmon工具监视服务器每秒的上下文切换次数。

    为了尽量减少上下文切换次数,最简单的做法就是减少进程数,尽量使用线程并配合其它I/O模型来设计并发策略。

    还可以考虑使用进程绑定CPU技术,增加CPU缓存的命中率。若进程不断在各CPU上切换,这样旧的CPU缓存就会失效。

    减少使用不必要的锁

    服务器处理大量并发请求时,多个请求处理任务时存在一些资源抢占竞争,这时一般采用“锁”机制来控制资源的占用,当一个任务占用资源时,我们锁住资源,这时其它任务都在等待锁的释放,这个现象称为锁竞争。

    通过锁竞争的本质,我们要意识到尽量减少并发请求对于共享资源的竞争。比如在允许情况下关闭服务器访问日志,这可以大大减少在锁等待时的延迟时间。要最大程度减少无辜的等待时间。

    这里说下无锁编程,就是由内核完成这个锁机制,主要是使用原子操作替代锁来实现对共享资源的访问保护 ,使用原子操作时,在进行实际的写操作时,使用了lock指令,这样就可以阻止其他任务写这块内存,避免出现数据竞争现象。原子操作速度比锁快,一般要快一倍以上。

    例如fwrite(), fopen(),其是使用append方式写文件,其原理就是使用了无锁编程,无锁编程的复杂度高,但是效率快,而且发生死锁概率低。

    考虑进程优先级

    进程调度器会动态调整运行队列中进程的优先级,通过top观察进程的PR值

    考虑系统负载

    可在任何时刻查看/proc/loadavg, top中的load average也可看出

    考虑CPU使用率

    除了用户空间和内核空间的CPU使用率以外,还要关注I/O wait,它是指CPU空闲并且等待I/O操作完成的时间比例(top中查看wa的值)。

    2. 考虑减少内存分配和释放

    服务器的工作过程中,需要大量的内存,使得内存的分配和释放工作尤为重要。

    可以通过改善数据结构和算法复制度来适当减少中间临时变量的内存分配及数据复制时间,而服务器本身也使用了各自的策略来提高效率。

    例如Apache,在运行开始时一次申请大片的内存作为内存池,若随后需要时就在内存池中直接获取,不需要再次分配,避免了频繁的内存分配和释放引起的内存整理时间。

    再如Nginx使用多线程来处理请求,使得多个线程之间可以共享内存资源,从而令它的内存总体使用量大大减少,另外,nginx分阶段的内存分配策略,按需分配,及时释放,使得内存使用量保持在很小的数量范围。

    另外,还可以考虑共享内存。

    共享内存指在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存,也可以由不同进程共享,是非常快的进程通信方式。

    但是使用共享内存也有不好的地方,就是对于多机器时数据不好统一。

    shell命令ipcs可用来显示系统下共享内存的状态,函数shmget可以创建或打开一块共享内存区,函数shmat将一个存在的共享内存段连接到本进程空间, 函数shmctl可以对共享内存段进行多种操作,函数shmdt函数分离该共享内存。

    3. 考虑使用持久连接

    持久连接也为长连接,它本身是TCP通信的一种普通方式,即在一次TCP连接中持续发送多分数据而不断开连接,与它相反的方式称为短连接,也就是建立连接后发送一份数据就断开,然后再次建立连接发送下一份数据, 周而复始。是否采用持久连接,完全取决于应用特点。从性能角度看,建立TCP连接的操作本身是一项不小的开销,在允许的情况下,连接次数越少,越有利于性能的提升; 尤其对于密集型的图片或网页等小数据请求处理有明显的加速所用。

    HTTP长连接需要浏览器和web服务器的共同协作,目前浏览器普遍支持长连接,表现在其发出的HTTP请求数据头中包含关于长连接的声明,如下: Connection: Keep-Alive

    主流的web服务器都支持长连接,比如apache中,可以用KeepAlive off关闭长连接。

    对于长连接的有效使用,还有关键一点在于长连接超时时间的设置,即长连接在什么时候关闭吗? Apache的默认设置为5s, 若这个时间设置过长,则可能导致资源无效占有,维持大量空闲进程,影响服务器性能。

    4. 改进I/O 模型

    I/O操作根据设备的不同分为很多类型,比如内存I/O, 网络I/O, 磁盘I/O. 对于网络I/O和磁盘I/O, 它们的速度要慢很多,尽管使用RAID磁盘阵列可通过并行磁盘磁盘来加快磁盘I/O速度,购买大连独享网络带宽以及使用高带宽网络适配器可以提高网络i/O的速度。但这些I/O操作需要内核系统调用来完成,这些需要CPU来调度,这使得CPU不得不浪费宝贵的时间来等待慢速I/O操作。我们希望让CPU足够少的时间在i/O操作的调度上,如何让高速的CPU和慢速的I/O设备更好地协调工作,是现代计算机一直探讨的话题。各种I/O模型的本质区别在于CPU的参与方式。

    1. DMA技术

    I/O设备和内存之间的数据传输方式由DMA控制器完成。在DMA模式下,CPU只需向DMA下达命令,让DMA控制器来处理数据的传送,这样可以大大节省系统资源。

    2. 异步I/O

    异步I/O指主动请求数据后便可以继续处理其它任务,随后等待I/O操作的通知,这样进程在数据读写时不发生阻塞。

    异步I/O是非阻塞的,当函数返回时,真正的I/O传输已经完成,这让CPU处理和I/O操作达到很好的重叠。

    3. I/O多路复用

    epoll服务器同时处理大量的文件描述符是必不可少的,若采用同步非阻塞I/O模型,若同时接收TCP连接的数据,就必须轮流对每个socket调用接收数据的方法,不管这些socket有没有可接收的数据,都要询问一次。假如大部分socket并没有数据可以接收,那么进程便会浪费很多CPU时间用于检查这些socket有没有可以接收的数据。多路I/O就绪通知的出现,提供了对大量文件描述符就绪检查的高性能方案,它允许进程通过一种方法同时监视所有文件描述符,并可以快速获得所有就绪的文件描述符,然后只针对这些文件描述符进行数据访问。

    epoll可以同时支持水平触发和边缘触发,理论上边缘触发性能更高,但是代码实现复杂,因为任何意外的丢失事件都会造成请求处理错误。

    epoll主要有2大改进:

    1. epoll只告知就绪的文件描述符,而且当调用epoll_wait()获得文件描述符时,返回并不是实际的描述符,而是一个代表就绪描述符数量的值,然后只需去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里使用了内存映射(mmap)技术,这样彻底省掉了这些文件描述符在系统调用时复制的开销。

    2. epoll采用基于事件的就绪通知方式。其事先通过epoll_ctrl()注册每一个文件描述符,一旦某个文件描述符就绪时,内核会采用类似callback的回调机制,当进程调用epoll_wait()时得到通知

    关于IO模型,可以参考笔者前面写的相关文章Java NIO.2; 关于epoll,可以参考笔者前面写的文章select、poll和epoll简介

    4. Sendfile

    大多数时候,我们都向服务器请求静态文件,比如图片,样式表等,在处理这些请求时,磁盘文件的数据先经过内核缓冲区,然后到用户内存空间,不需经过任何处理,其又被送到网卡对应的内核缓冲区,接着再被送入网卡进行发送。

    Linux提供sendfile()系统调用,可以讲磁盘文件的特定部分直接传送到代表客户端的socket描述符,加快了静态文件的请求速度,同时减少CPU和内存的开销。

    适用场景: 对于请求较小的静态文件,sendfile发挥的作用不那么明显,因发送数据的环节在整个过程中所占时间的比例相比于大文件请求时小很多。

    5. 内存映射

    Linux内核提供一种访问磁盘文件的特殊方式,它可以将内存中某块地址空间和我们指定的磁盘文件相关联,从而对这块内存的访问转换为对磁盘文件的访问。这种技术称为内存映射。

    多数情况下,内存映射可以提高磁盘I/O的性能,无须使用read()或write()等系统调用来访问文件,而是通过mmap()系统调用来建立内存和磁盘文件的关联,然后像访问内存一样自由访问文件。

    缺点:在处理较大文件时,内存映射会导致较大的内存开销,得不偿失。

    6. 直接I/O

    在linux 2.6中,内存映射和直接访问文件没有本质差异,因为数据需要经过2次复制,即在磁盘与内核缓冲区之间以及在内核缓冲区与用户态内存空间。

    引入内核缓冲区的目的在于提高磁盘文件的访问性能,然而对于一些复杂的应用,比如数据库服务器,它们为了进一步提高性能,希望绕过内核缓冲区,由自己在用户态空间实现并管理I/O缓冲区,比如数据库可根据更加合理的策略来提高查询缓存命中率。另一方面,绕过内核缓冲区也可以减少系统内存的开销,因内核缓冲区本身就在使用系统内存。

    Linux在open()系统调用中增加参数选项O_DIRECT,即可绕过内核缓冲区直接访问文件,实现直接I/O。

    在Mysql中,对于Innodb存储引擎,自身进行数据和索引的缓存管理,可在my.cnf配置中分配raw分区跳过内核缓冲区,实现直接I/O。

    改进服务器并发策略

    服务器并发策略的目的,是让I/O操作和CPU计算尽量重叠进行,一方面让CPU在I/O等待时不要空闲,另一方面让CPU在I/O调度上尽量花最少的时间。

    一个进程处理一个连接,非阻塞I/O

    这样会存在多个并发请求同时到达时,服务器必然要准备多个进程来处理请求。其进程的开销限制了它的并发连接数。但从稳定性和兼容性的角度,则其相对安全,任何一个子进程的崩溃不会影响服务器本身,父进程可以创建新的子进程;这种策略典型的例子就是Apache的fork和prefork模式。对于并发数不高(如150以内)的站点同时依赖Apache其它功能时的应用选择Apache还是可以的。

    一个线程处理一个连接,非阻塞IO

    这种方式允许在一个进程中通过多个线程来处理多个连接,一个线程处理一个连接。Apache的worker模式就是这种典型例子,使其可支持更多的并发连接。不过这种模式的总体性能还不如prefork,所以一般不选用worker模式。

    一个进程处理多个连接,异步I/O

    一个线程同时处理多个连接,潜在的前提条件就是使用IO多路复用就绪通知。

    这种情况下,将处理多个连接的进程叫做worker进程或服务进程。worker的数量可以配置,如Nginx中的worker_processes 4。

    一个线程处理多个连接,异步IO

    即使有高性能的IO多路复用就绪通知,但磁盘IO的等待还是无法避免的。更加高效的方法是对磁盘文件使用异步IO,目前很少有Web服务器真正意义上支持这种异步IO。

    6. 改进硬件环境

    还有一点要提及的是硬件环境,服务器的硬件配置对应用程序的性能提升往往是最直接,也是最简单的方式,这就是所谓的scale up。这里不做论述。

  • 相关阅读:
    JVM——类加载
    Java IO输入输出
    核心标签库和el
    request对象
    安装tomcat
    安装mongodb
    MySQL在简单命令行操作
    安装MySQL
    Java几种常见的异常类型
    Java简单正则表达式写爬虫
  • 原文地址:https://www.cnblogs.com/strawqqhat/p/10602212.html
Copyright © 2020-2023  润新知