java知识点小结

java面试

异常处理和IO操作

打断正常流程的情况分为两类,错误Error和异常Exception,这两个都是继承自Throwable类的子类。对于错误,我们一般不需要进行干预,直接终止程序。对于异常需要进行处理。

异常处理定式 try…catch…finally

除非使用exit退出程序,其他情况finally都会执行。
运行期异常不必包含在try中

  • throw,throws和throwable的关系
    throw抛出异常,throws写在方法声明部分参数列表面,指示方法可能抛出的异常。
    throwable是公共Exception和Error的公共基类。

异常部分知识点

  • finally中应该放内存回收相关代码
  • 在子类中不应该抛出比父类范围更广的异常
  • 尽量减少try监控区域
  • 先用具体的异常类处理,最后用Exception。
  • catct中尽可能处理异常。
  • 出现异常后尽量保证项目不会终止。两个平行的业务其中一个出现异常不应该影响另外一个。

常见的IO读写操作

  • File对象
File file = new File(path)
File flist[]=file.listFiles();
  • Java中的流
    • 流分为字节流和字符流
      • 字节流
        用来操作二进制文件,有两个接口,InputStreamOutputStream,实现类有FileInputStreamBufferedInputStream,FileOutputStream,BufferedOutputStream四个。
      • 字符流
        用来操作文本文件,有两个接口,ReaderWriter,实现类有FileReader,FileWriter,BufferedReader,BufferedWriter四个还有InputStreamReader,OutputStreamReader。
    • FileInputStreamFileOutputStream字节流
    FileInputStream fileInputStream=new FileInputSteam(new File(path));
    // 每次读取一个字节
    int res=fileInputStream.read();
    // 每次读取一个字节数组,并返回读取到的字节数量
    byte[] bytes=new byte[1024];
    int len=fileInputStream.read(bytes);
    // byte[]转String
    String str=new String(bytes);
    
    • InputStreamReader传入的参数由byte[]变成char了,char占两个字节。第二个参数可以传入编码
    • BufferedReader,需要接受一个字符流InputStreamReader,然后调用readLine方法。
    FileInputStream fileInputStream = new FileInputStream(file);
    InputStreamReader bufferedReader=new InputStreamReader(inputStreamReader);
    BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
    line=bufferReader.readLine();
    
    • 如图所示
      image
    • 非阻塞性的NIO操作
      NIO操作是面向缓存的,而不是面向流的。NIO的三大重要组件是ChannelBufferSelector
      通道是双向的,是单向的。
      channel对象有4种:FileChannelDatagramChannelSocketChannelServerSocketChannel
      在NIO中,程序员往往会配套使用Buffered和Channel,Channel用来读写操作,Buffered用来缓存读写的内容。
      • 通道读写文件例子
      String filepath="D:/xxxx.txt";
      File file=new File(filepath);
      String dstpath="D:/xxxxx.txt";
      File dstf=new File(dstpath);
      int bufferSize=1024;
      FileChannel src=new FileInputStream(file).getChannel();
      FileChannel dst=new FileOutputStream(dstf).getChannel();
      ByteBuffer buffer=ByteBuffer.allocate(bufferSize);
      while(src.read(buffer)!=-1){
          buffer.flip();
          dst.write(buffer);
          buffer.clear();
      }
      
      • 选择器
        • 打开选择器Selector selector=Selector.open();
        • Channel和选择器使用时,需要把channel切换到非阻塞状态,channel.configureBlocking(false);
        • 将通道注册到选择器
          SelectionKey key = channel.register( selector, Selectionkey.OP_READ);不同的参数值用|分开。如果返回对应的状态码,表示对应状态就绪。
        • int val=selector.select();返回可以使用的通道数量。
        • 通过selector.selectedKeys()返回所有可以操作的通道对象。使用迭代器遍历,通过key.isReadablekey.isWritable判断状态。使用完后,要remove()否则下次遍历还会遍历到。

解析XML文件

总体来说,有两种方式,基于DOM的基于SAX的

  • 基于DOM的
    由于基于DOM的方式会将整个文件载入内存,所以可以边解析边修改。而且可以解析已经解析过的内容。
  • 基于SAX的
    基于回调函数的方式解析的,因此并不需要把整个文档载入内存,这样可以节省内存资源。解析速度快。适用于比较大的XML文件。

SQL,JDBC与数据库编程

  • sql语句的执行时间 = 数据库服务器执行该sql语句的时间 + 结果返回时间

  • count(*)和count(字段名)区别:当字段值为null时,count(字段名)不计数。count(字段名)比count(*)快。推荐使用count(主键)的方式。

  • insert尽量不要省略字段列表;sql server和mysql支持多条插入,oracle不支持多条插入。一次性插入条数在500到1000条之间比较好。

  • delete可以通过in语句同时删除多个记录,但是同时删除的记录数不能太多。

  • 存在更新,不存在插入 merge mysql不支持,sql server,oracle支持。

merge into 表1 a using 源表 b on (a.字段1=b.字段2)
  when matched then update set a.字段=b.字段
  when not matched then insert into a(字段列表) values(值列表)

关于存储过程的分析

存储过程只在创建时进行编译,以后每次执行都不需要重新编译。缺点是存储过程可移植性差。难以调试发现错误。对比jdbc的批处理和存储过程,存储过程没有太大优势。

通过JDBC开发读写数据库的代码

JDBC屏蔽了数据库的实现细节和差异,给程序员提供了比较统一的调用方式。

开发步骤

  1. 装载驱动Class.forName("com.mysql.jdbc.Driver")

  2. 创建连接conn=DriverManager.getConnection(url,username,pwd)

  3. 创建statement对象:stmt=conn.createStatement();

  4. 执行查询语句:ResultSet result=stmt.executeQuery(sql);

    执行删除更新插入语句:\

    • 第一种int affectRows=stmt.executeUpdate(sql)
    • 第二种
     ps=conn.prepareStatement(updatesql);
     int affectRows=ps.executeUpdate();
    
  5. 遍历取结果:result.next() result.getString("id") result.getInt("字段名")

优化数据库部分代码

  • 把相对固定的连接信息写入配置文件中
    properties文件 一般项目中有两种环境,开发环境和生产环境
    通过Properties读取properties文件代码:
Properties prop=new Properties();
InputStream in=null;
try{
    in=new BufferedInputStream(new FileInputStream(path));
    prop.load(in);
    字段=prop.getProperty("name")
}catch(IOException e){e.printStackTrace();}
finally{
    if(in!=null){
        try{
            in.close();
        }catch(IOException e){e.printStackTrace();}
        prop.clear();
    }
}

用PreparedStatement以批处理的方式操作数据库

  • 在sql语句中需要传参数的地方用作为占位符
  • 调用PreparedStatement pstmt=conn.preparedStatement(sql);创建对象;
  • 放入参数pstmt.setString(参数序号,参数值);pstmt.setInt(参数序号,参数值)参数序号从1开始
  • 每放入一组参数,执行pstmt.addBatch()进行缓存(500-1000条记录为宜)
  • 批处理结束,执行pstmt.executeBatch();

C3P0连接池

代码在初始化连接池是会初始化一定量的连接对象,在程序中外部使用者获得这些连接对象并使用,使用完毕这些对象仍然保存,避免因频繁创建和释放数据库连接对象而带来的性能损耗。

ComboPooledDataSource ds=new ComboPooledDataSource();
try{
    ds.setDriverClass("com.mysql.jdbc.Driver");
}catch(PropertyVetoException e){e.printStackTrace();}
ds.setJdbcUrl()
ds.setUser()
ds.setPassword()
ds.setMaxPoolSize()
ds.setMinPoolSize()
ds.setInitialPoolSize()

最大生存时间和最大空闲时间应该尽可能大,否则可能出现数据库操作过程中连接丢失。
通过下面方式获取数据库连接对象ds.getConnection()

JDBC事务

  • 开启事务
  • 设置是否开启事务conn.setAutoCommit(false);
  • 提交conn.commit()
  • 回滚conn.rollBack()
事务中的常见问题:
  • 脏读 读到别的事务要回滚的数据(其他事务不可读取该事物修改未提交的值)
  • 幻读 一个事务的操作导致另外一个事务中前后两次查询结果不同(添加问题 操作事务完成之前,其他事务不可添加新数据。)
  • 不可重复读 一个数据的操作导致另外一个事务前后两次读到不同的数据。
事务隔离级别
  • 读取未提交(脏读、幻读、不可重复读)
  • 读取提交(幻读、不可重复度)
  • 可重读(幻读)
  • 可串行化(都可避免)
    比较严格的要求,往往会牺牲性能为代价。

索引

索引分为单列索引和组合索引。
索引的实质也是一张表,该表中保存了主键和索引字段,并指向实体表的记录。

  • 创建索引,create index indexname on table(字段名(索引长度))
  • 修改表结构创建索引alter table tablename add index indexname(colname)
  • 创建表的时候直接指定
    create table mytable(字段列表,index indexname (字段名(长度)))
    删除索引 drop index indexname on tablename
  • 唯一索引
    create unique index indexname on tablename(字段名(长度))
    alter table tablename add unique indexname (字段名(长度))
    create talbe tablename( unique indexname (字段名(长度)))
    查看索引
    show index from tablename

反射机制和代理模式

反射机制实现的基础Class类
当一个类被装入java虚拟机(jvm)时,便会产生一个与之关联的Class对象。通过Class.forName()我们也可以获得指定类的Class对象。
反射机制:通过java的字节码获得里面的属性方法等信息,并调用方法。
反射的常见用法:查看某个类的属性方法等信息;装载指定的类到内存;通过输入参数,调用指定的方法。

  • 查看
class Student{
    private int Id;
    private String Name;
}
Class<Student> clazz=Student.class
Fields[] fields=clazz.getDeclaredFields();
Methods[] methods=clazz.getDeclaredMethods();
  • 装载
Class<?> clazz=Class.forName("Student")

newInstancenew都可以得到一个可用的对象,但是newInstance是通过Java虚拟机的类加载机制把指定的类加载到内存中。但newInstance只能调用无参构造函数,若要使用有参数的构造函数,得用new

  • 调用

代理模式和反射

静态代理

三种角色的定义:抽象角色、真实角色、代理角色
抽象角色是真实角色与代理角色的共同接口;真实角色是要被引用的角色;代理角色中包含真实对象的引用。
代理模式的附带优点是降低代码耦合度。使用代理模式的场景是为了更快或者更便宜的方式得到某些服务。

动态代理

通过反射实现的,在运行时动态的生成代理类。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Service {
    String sellCar(String carName);
}

class ServiceImpl implements Service {
    public String sellCar(String carName) {
        return carName + " is ready!";
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object target;
    MyInvocationHandler(Object target){
        this.target=target;
    }
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("call:"+method.getName());
        Object result=method.invoke(target,objects);
        return result;
    }
}

public class ReflectDemo {
    public static void main(String[] args) {
        Service service=new ServiceImpl();
        InvocationHandler invocationHandler=new MyInvocationHandler(service);
        Service proxy=(Service) Proxy.newProxyInstance(service.getClass().getClassLoader(),service.getClass().getInterfaces(),invocationHandler);
        System.out.println(proxy.sellCar("hello"));
    }
}

通过invoke方法进行调用,便于aop,可以在调用方法之前或者之后加上相关代码。
动态代理实现方法
实现InvocationHandler接口并重写invoke方法。实现含一个参数的构造函数Object类型。

多线程与并发编程

多线程实现的两种方法
继承Thread类:

class SimpleThread extends Thread{
    public void run(){
        
    }
}

实现Runnable接口:

class ThreadPriority implements Runnable{
    int number;
    public ThreadPriority(){
        
    }
    public void run(){
        
    }
    public static void Main(String[] args){
        Thread t1=new Thread(new ThreadPriority(1);
        t1.setPriority(5)
        t1.start();
    }
}

java中优先级从高到底1-10
调用sleep必须包含在try catch中。
编程时考虑多线程,操作系统考虑多进程。

线程生命周期

  • 初始化状态:使用new创建对象,没有调用start方法。
  • 调用了start方法之后,可执行状态
  • 阻塞状态:调用了sleep或者wait
  • 死亡状态:调用stop或者destory
  • 阻塞状态到可执行状态转换:调用notify或者notifyAll

多线程竞争和同步

sleep和wait的区别:

  • sleep是线程的方法,wait是Object的方法。
  • sleep不会释放lock,但是wait会释放lock,而且会加入到等待队列。
  • sleep不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
  • sleep不需要被唤醒,但是wait需要。

synchronized可以作用于方法上,放在方法名前面。

相当于给调用方法的对象加上了锁。例如o.add(),给o加锁后不会有其他对对象争用o;

synchronized作用于代码块

代码块中的代码同一时间只能有一个线程访问。
一旦一个线程执行完wait方法后,会释放所有锁,然后进入阻塞状态,无法主动回到可执行状态。一定要通过其他线程的notify或者notifyAll唤醒。
synchronized:作用在方法上的局限是只能保证方法层面的排他性,不能保证业务层面的排他性。

ReentrantLock()可重入锁,递归锁

Lock lock=ReentrantLock();
lock.lock();
lock.unlock();
还可以使用lock.tryLock()方法加锁,如果失败返回false,否则返回true。不会无限等待。

Condition实现线程间的通信

await()让自己等待,释放相应的对象锁。
signalAll()唤醒其他线程。
Condition对象是由Lock对象生成的,condition=lock.newCondition();可以实现精准唤醒。

通过Semaphore管理多线程的竞争

计数信号量。可以用来限制访问特定资源的线程数量。

  • 构造函数,public Semaphore(int permits,boolean fair)fair为true保证先来先服务。
  • 获得访问权的方法public void acquire() throws InterruptedException
  • 释放资源public void release()
    创建可重入锁,也可以指定其是否公平。

ReentrantReadWriteLock读锁和写锁。读写锁可以在保证并发时数据准确的前提下允许多个线程读。

readlock,writelock

volatile的作用

每次不是从线程内部的内存获取值,而是从主存获取值,避免创建副本。提高效率

ThreadLocal只在本线程有效

通过ThreadPoolExecutor实现线程池

ThreadPoolExecutor executor=new ThreadPoolExecutor(corePoolSize,maxPoolSize,long keepAliveTime,TimeUnit,workQueue,rejectexecutionHandler)
然后使用executor.execute(thread)执行

当线程池中线程数小于corePoolsize时,为新来的任务开启新线程;
当线程池达到corePoolSize时,新到的线程放入workQueue中;
当workQueue满且maximumPoolSize>corepoolsize时,会为新到的线程创建新的执行线程;
当线程总数超过maximumPoolSize时,启用拒绝策略;
当线程总数超过corepoolsize时,空闲时间等于keepaliveTime,关闭空闲线程。

通过callable让线程返回结果

import java.util.concurrent.*;

class Task implements Callable<Integer>{

    public Integer call() throws Exception {
        return 0;
    }
}

public class ReflectDemo{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor= Executors.newCachedThreadPool();
        Task task=new Task();
        Future<Integer> result=executor.submit(task);
        System.out.println(result.get());
    }
}

通过Executors创建4种类型的线程池

  • newCachedThreadPool 创建可缓存线程池
  • newFixedThreadPool 创建定长线程池
  • newScheduledThreadPool 创建定长线程池,支持周期性的任务
  • newSingleThreadExecutor 创建单线程线程池

创建多线程的方法

  • 通过继承Thread
  • 通过实现Runnable,由于java中不支持多继承,所以实现Runnable方式创建多线程用的比较多。
  • 通过实现Callable,线程执行结束的时候就可以返回结果了。使用Future<?>来接收返回结果,并用get方法取值。

从线程内存角度分析并发情况

从内存角度分析并发结果不一致的原因

如果某个线程要操作data变量,线程会先把data变量装载到线程内部的内存中做一个副本,之后线程就不再和主存内的data变量有任何关系,而是操作副本。操作完后再写回主内存。

线程不安全的集合

如果项目运行在单线程环境下,建议选择线程不安全的集合。否则,选择线程安全的集合。可以通过Collections.synchronizedList(new ArrayList())的方法把线程不安全的包装成线程安全的。

虚拟机内存优化

字节码、虚拟机、JRE、跨平台

java代码编译成字节码,运行在虚拟机上,虚拟机屏蔽了不同操作系统的差异,保证了跨平台,实现了装载字节码并执行。JRE包含java虚拟机和核心类库。

虚拟机体系结构

  • 装载子系统
    主要分为启动类加载器(BootstrapClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(ApplicationClassLoader)、用户自定义的类加载器(User Defined ClassLoader)
    和类加载相关的两个异常:加载时找不到类ClassNotFoundException;如果加载的类引用的类找不到NoClassDefFoundExpection
  • 运行时数据区:方法区、堆区、栈区、程序计数器、本地方法栈模块
  • 执行引擎和本地接口
  • 本地接口调用操作系统本地库
    优化虚拟机主要是优化堆区内存
  • 静态数据、基本数据类型、引用等存放位置:
    静态类型存放在方法区
    new出来的对象存放在堆区
    指向new出来对象的引用存放在栈区
    基本数据类型数据和引用都存放在栈区
    常量类数据存放在方法区的常量池中

java垃圾回收机制

从Eden到两个Survivor、Tenured,触发的垃圾回收都是轻量级回收,标记复制算法。
当Tenured满了后,会启动full gc,对所有区域进行回收,耗费时间较长。
启动full gc的情况:
年老代被写满;
持久带被写满
程序员显式调用System.gc(不会立即执行,而是系统在合适的时间执行。)
在执行full gc时,会导致stop the world现象。
stop the world:只有gc线程在运行,其他线程都挂起。为了保证在复制存活对象时对象的一致性。

判断对象可回收的依据

引用计数值方法:引用计数值等于0时可回收;缺点是不能回收循环引用。
根搜索算法:从gc root出发,回收不可达的对象。
可以作为gc root的对象:栈中引用对象、静态属性引用的对象、常量引用的对象、本地方法栈引用的对象。

finalize方法

如果不重写finalize方法,直接回收;否则,将finalize方法放入F-Queue队列。另一个线程会定时遍历F-Queue队列。在finalize方法执行完毕后,gc会再次判断该对象是否有强引用,如果有则复活,否则,回收。

强、弱、软、虚四种引用

强引用不会被回收
软引用如果空间够不会被回收
适合做缓存,如果空间够,则不回收,否则回收掉
弱引用会被回收
弱引用具有自动更新的功能(主要是自动删除)。
虚引用
可以实现在回收后销毁对象。
虚引用和引用队列组合使用。
在gc后可以在引用队列中找到对象。
及时释放内存

stop the world、栈溢出、内存溢出错误

stop the world内存占用量很高的情况下容易不停地stop the world,导致程序无法正常工作。
栈溢出尽量避免递归或者控制递归深度。
内存溢出错误new thread无法创建线程,new无法创建对象,堆空间不足,类加载器加载时持久区不够。

内存泄漏

  • 一些数据库或IO连接没有关闭
  • new了空间没有删除引用
    创建list时放入5个string,然后取出来,引用2次。取出来设置为null,引用减1,但还是在list中引用,无法被释放。

内存优化具体做法

  • 物理对象(connection或IO对象)用完及时close()
  • 使用完对象指向null obj=null
  • 不应该频繁操作String
  • 集合对象用完后及时clear()
  • 合理使用软引用,弱引用。
    -调整运行参数
    • Xms设置程序初始堆大小
    • Xmx设置程序最大堆大小
      (其他的一般不改)

外部类和内部类是平行的,有可能外部类比内部类更早被释放掉。所以内部类使用外部类时,参数前面要加final,放在常量池内。

猜你喜欢

转载自blog.csdn.net/weixin_44583265/article/details/106531339