总结每天10问之Java基础3

Java 基础

OOP相关

21、讲讲类的实例化顺序?

答:

  • 执行clinit方法

    什么是clinit得等到jvm篇讲解的时候讲方便点,这里就记住clinit是class的static 变量,final变量,static代码块这些就好。clinit方法不是每个class都有的,Object就没有。

    包括两个步骤:

    1)执行父类的clinit方法。

    2)执行子类的clinit方法。

    clinit里面具体的执行逻辑是:

    ① 执行static filed

    ②执行static 代码块

  • 执行init方法(构造函数)

    1)执行父类的init方法。什么是init也是得等到jvm篇讲解的时候说比较方便,这里记住init方法就是我们class的构造函数就好。

    2)执行子类的init方法。

执行完构造函数之后,jvm会把赋值的对象和构造的对象关联起来,这样就完成了父类的实例化。

22、char能否存一个中文字符

答:

能,C++中char是1byte,但是在Java中Char采用ACSII编码,char是2bytes,所以Java中char能保存两位字符。

23、error 和 exception 有什么区别?

答:

error是jvm的底层自己内部的异常。

exception是jdk之上的异常,分受检异常和非受检异常。受检异常是调用者需要try……catch的异常,非受检异常是用户不需要try……catch的异常。RuntimeException都是非受检异常。

受检异常:CheckedException,非受检异常:RuntimeException。

24、反射的用途

答:

在日常使用里面我们会有一种需求:需要在运行的时候知道某个对象里面有什么字段,需要在执行的某方法方法的前后执行一下一些统一逻辑。

JDK在解析字节码(class)的时候,保留了字节码的描述,保存在内存的方法区。JDK为了满足我们上面类似需求提供给了我们一些方式,让我们能获取字节码的描述,然后根据自己的需求来实现自己想要的功能。

25、反射的三种方式

答:

  1. 类.class。

    比如

    Class<?> clazz = A.class
    
  2. 对象.getClass()

    比如:

    Class<?> clazz = a.getClass()
    
  3. Class.forName()

    比如:

    Class<?> clazz = Class.forName("com.jdbc.xxxx")
    
26、什么时候用断言

答:

断言其实是我们告诉编译器,我们业务逻辑上保证这个东西是正确的。比如保证某个东西一定不会为null

assert obj != null;

在我们的平时开发/测试的时候,我们在运行jvm的时候加上参数 -ea ,断言就生效了,会帮我们做一些判断,执行的逻辑不符合断言就会抛出一些异常。生产环境需要去掉。

我也自己封装了一套断言程序:


/**
 * 这个类是用来判断对象是否为空的。断言工具 <br/>
 *
 * 时间 2020-08-06
 * @author 袁小黑
 */
public enum AssertUtils {

    ;

    /**
     * 使用枚举类的原因是因为这个类是一个工具类,防止被别人初始化
     */

    public static void isNotNull(Object obj, String msg, Object...pars) {
        if (obj == null) {
            throw new RuntimeException(MessageFormatter.arrayFormat(msg, pars).getMessage());
        }
    }

    public static void isTrue(Boolean isTrue, String msg, Object...pars) {
        if (isTrue == null || !isTrue) {
            throw new RuntimeException(MessageFormatter.arrayFormat(msg, pars).getMessage());
        }
    }

    public static void isNotBlank(String str, String msg, Object...pars) {
        if (StringUtils.isBlank(str)) {
            throw new RuntimeException(MessageFormatter.arrayFormat(msg, pars).getMessage());
        }
    }

    public static void isNotEmpty(Collection<?> collection, String msg, Object...pars) {
        if (collection == null || collection.isEmpty()) {
            throw new RuntimeException(MessageFormatter.arrayFormat(msg, pars).getMessage());
        }
    }

    public static void isNatureNumber(int num, String msg, Object...pars) {
        if (num < 0) {
            throw new RuntimeException(MessageFormatter.arrayFormat(msg, pars).getMessage());
        }
    }

}

17、Java IO 的理解

答:

Java IO,什么是IO。IO:input、output。输入输出,其实描述的是数据从一个地方到另外一个地方的逻辑,Java IO 它站的是内存的角度。比如从内存到磁盘,从内存到网络,这个就是java io。

从技术角度上讲Java IO主要分类字节流、字符流。为什么要分字符流和字节流呢?

字符:在java中的字符就是char,采用的是ASCII编码处理Unicode字符,**Java为了让我们更加方便地操纵Unicode字符,故意加多了一种IO方式。**之前谈过Java中一个char可以存一个中文字符,那么使用字符IO就不会出现传输过程中出现异常情况的时候导致某个字只传了一半(当然,这里不考虑网络IO的问题。)字符编码都是从Reader和Writer抽象类中基础出来的。只能操纵文本类型的文件,如果使用Reader和Writer操作音频/视频文件就会出现文件损坏的情况。

字节:最基本的流,适用于所有的情况。字节输入流输出流,其实没有缓存,直接使用了磁盘IO,一般都会很慢的,字符流还是用了缓存,性能比字节流强很多。实际操作文件直接使用字节流的比较少。

使用了缓存和不使用缓存的差别还是很大的,下面是我的测试用例:

//write elapse time : 3127
//read elapse time : 24525
@Test
public void test1() {
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);

    try (FileInputStream fis = new FileInputStream(rootPath+ File.separator+"test.txt");
         FileOutputStream fos = new FileOutputStream(rootPath+ File.separator+"test.txt", true)
        ) {
        long statBegin = System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            fos.write("xxxxxxxxxxx\n".getBytes());
        }
        System.out.println("write elapse time : "+(System.currentTimeMillis() - statBegin));
        int b;
        statBegin = System.currentTimeMillis();
        while ((b = fis.read()) != -1) {
            //System.out.print((char)b);
        }
        System.out.println("read elapse time : "+(System.currentTimeMillis() - statBegin));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//write elapse time : 78
//read elapse time : 479
@Test
public void test() {
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);
    try (OutputStreamWriter fw = new FileWriter(rootPath+File.pathSeparator+"test.txt");
         InputStreamReader fr = new FileReader(rootPath+File.pathSeparator+"test.txt")
        ) {
        long statBegin = System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            fw.write("xxxxxxxxxxx\n");
        }
        System.out.println("write elapse time : "+(System.currentTimeMillis() - statBegin));
        int b;
        statBegin = System.currentTimeMillis();
        while ((b = fr.read()) != -1) {
            //System.out.print((char)b);
        }
        System.out.println("read elapse time : "+(System.currentTimeMillis() - statBegin));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

很简单的一段逻辑,对test.txt文件写1百万次和读1百万次,写的场景:字符流花了78ms,字节流花了3127ms,相差了40+倍的时间,读的场景:字符流479ms,字节流花了24525ms,相差也是接近50倍,这就是缓存的魅力。参考apache的commons-io,建议使用字符流的时候自己加上一个buffer。

字符流字节流之下都可以再分输入流/输出流。流又设计序列化的问题,IO这一块的复杂接触之后就能知道,还是那句话:没有最完美的方案,只有最适合的方案。很多种IO提出来之后是为了解决某个问题,我们要具体看这个解决方案适不适合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5AdcG2BE-1604224920316)(java%E5%9F%BA%E7%A1%803.assets/1018541-20170317090935213-142491173.png)]

如果平时需要性能比较高的场景,建议使用:缓冲流,有:BufferedInputStreamBufferOutputStreamBufferedReaderBufferWriter

同样是读写1百万次,下面是针对Buffer类型的流进行的一组实验:

//write elapse time : 57
//read elapse time : 58
@Test
public void test1() {
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);

    try (FileInputStream fis = new FileInputStream(rootPath+ File.separator+"test.txt");
         FileOutputStream fos = new FileOutputStream(rootPath+ File.separator+"test.txt", true);
         BufferedInputStream bis = new BufferedInputStream(fis);
         BufferedOutputStream bos = new BufferedOutputStream(fos);
        ) {
        long statBegin = System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            bos.write("xxxxxxxxxxx\n".getBytes());
        }
        System.out.println("write elapse time : "+(System.currentTimeMillis() - statBegin));
        int b;
        statBegin = System.currentTimeMillis();
        while ((b = bis.read()) != -1) {
            //System.out.print((char)b);
        }
        System.out.println("read elapse time : "+(System.currentTimeMillis() - statBegin));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//write elapse time : 45
//read elapse time : 53
@Test
public void test() {
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);
    try (OutputStreamWriter fw = new FileWriter(rootPath+File.pathSeparator+"test.txt");
         InputStreamReader fr = new FileReader(rootPath+File.pathSeparator+"test.txt");
         BufferedReader br = new BufferedReader(fr);
         BufferedWriter bw = new BufferedWriter(fw);
        ) {
        long statBegin = System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            bw.write("xxxxxxxxxxx\n");
        }
        System.out.println("write elapse time : "+(System.currentTimeMillis() - statBegin));
        int b;
        statBegin = System.currentTimeMillis();
        while ((b = br.read()) != -1) {
            //System.out.print((char)b);
        }
        System.out.println("read elapse time : "+(System.currentTimeMillis() - statBegin));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

从上面的代码可以看出:写的场景:对于字节流来说使用缓存几乎提升了40+倍的性能(elapse time 表示运行这段程序花费的时间),对于字符流来说,其内核已经实现了缓存,使用缓存流提升不是很大,但也是接近了一倍。读的场景:性能都有很大提升。其实使用缓存可靠性就会低点,所有的东西都取决于业务需求。

其实上面的样例都是顺序读写。

18、Java NIO的理解

答:

NIO:new io。是jdk4之后引入的新类型的IO,主要解决访问io资源的过程中的性能问题。

Nio的三个核心部件有:SelectorChannelBuffer

Channel 有FileChannel,NioChannel,DatagramChannel等,不同的需求使用不同的Channel。

Buffer就更加多了,可以说是多到恐怖的境界,几乎一种数据类型一种buffer,最重要的是ByteBuffer

Selector 只有一种,这点可以放心。

FileChannel是在文件方面的nio,其它的Channel都是网络协议的Channel。FileChannel的应用非常广泛,RocketMq的commitLog就是使用FileChannel进行mmap文件到磁盘的映射。

这里针对上面的17问,来做一个实验,也是针对一个文件写读1百万次:

//write elapse time : 3023
//read elapse time : 3
@Test
public void writeAndRead() {
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);

    String targetFile = rootPath+ File.separator+"test.txt";
    // 分配1MB的缓存空间
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);
    try (FileInputStream fis = new FileInputStream(rootPath+ File.separator+"test.txt");
         FileOutputStream fos = new FileOutputStream(rootPath+ File.separator+"test.txt")
        ) {
        FileChannel channel = fos.getChannel();
        FileChannel rcfChannel = fis.getChannel();
        long statBegin = System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            byteBuffer.put("xxxxxxxxxxx\n".getBytes());
            byteBuffer.flip();
            channel.write(byteBuffer);
            byteBuffer.clear();
        }
        System.out.println("write elapse time : "+(System.currentTimeMillis() - statBegin));
        int b;
        byteBuffer.flip();
        statBegin = System.currentTimeMillis();
        while (rcfChannel.read(byteBuffer) != -1) {
            byteBuffer.flip();
            //System.out.println(getString(byteBuffer));
            byteBuffer.clear();
        }

        System.out.println("read elapse time : "+(System.currentTimeMillis() - statBegin));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

最后结果是:写的时间变成了3s,但是读的时间直接变成了3ms。其实17问给了例子都是顺序读写,顺序读写的性能是比随机读写快很多的,ES也是依据顺序读写去实现的。使用FileChannel读的性能提高是非常明显的。

FileChannel还有很多妙用,我们来试试传说中的零拷贝。

mmap()

//write elapse time : 74
//read elapse time : 12
@Test
public void mmap() {
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);

    String targetFile = rootPath+ File.separator+"test.txt";

    try (FileChannel channel = FileChannel.open(Path.of(targetFile), EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.READ));
         FileChannel rcfChannel = FileChannel.open(Path.of(targetFile), EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.READ));
        ) {

        int fileSize = 30 * 1024 * 1024;
        MappedByteBuffer writeMappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
        long statBegin = System.currentTimeMillis();
        for (int i = 0; i < 100_0000; i++) {
            writeMappedByteBuffer.put("xxxxxxxxxxx\n".getBytes());
        }
        writeMappedByteBuffer.force();
        System.out.println("write elapse time : "+(System.currentTimeMillis() - statBegin));
        MappedByteBuffer readMappedByteBuffer = rcfChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
        statBegin = System.currentTimeMillis();
        //byte[] ds = new byte[fileSize];
        for (int offset = 0; offset < fileSize; offset++) {
            ByteBuffer b = readMappedByteBuffer.slice(0, fileSize);
            //System.out.println(getString(b));
        }
        System.out.println("read elapse time : "+(System.currentTimeMillis() - statBegin));
        //System.out.println(new String(ds));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

从上面可以看出零拷贝的mmap方式花费的时间是写场景花的是74ms,读场景花的时间是12ms。在读的时候,它具有很大的优势,因为它可以随意读,只需要知道你需要的数据在buffer的哪个偏移量那里,它的缺点是:需要消耗大量的内存,这也是空间换时间的精髓。

和我们在17问做的东西有很大的不同之处,因为我们在17问做的实验都是顺序读写,在随机读写中我们一般使用RandomAccessFile 来实现,RandomAccessFile 配合FileChannel实现零拷贝,技术更加完美;缺点也是需要大量的内存。

而且现实业务很多都是随机读写的,RandomAccessFile在针对大文件处理的时候非常有优势。RocketMQ的消息采用顺序写到commitlog文件,然后利用consume queue文件作为索引;RocketMQ采用零拷贝mmap+write的方式来回应Consumer的请求;和我们上面的实验代码非常相似。

零拷贝还有一种方式就是:transferTo和transferFrom。Kafka使用的就是transfer和transferFrom,这两种拷贝方式在性能上比mmap的零拷贝方式还快。但是tranferTo和transferFrom 应对的需求是:经常需要从一个位置将文件传输到另外一个位置。FileChannel提供了transferTo()方法用来提高传输的效率。

ile` 配合FileChannel实现零拷贝,技术更加完美;缺点也是需要大量的内存。

而且现实业务很多都是随机读写的,RandomAccessFile在针对大文件处理的时候非常有优势。RocketMQ的消息采用顺序写到commitlog文件,然后利用consume queue文件作为索引;RocketMQ采用零拷贝mmap+write的方式来回应Consumer的请求;和我们上面的实验代码非常相似。

零拷贝还有一种方式就是:transferTo和transferFrom。Kafka使用的就是transfer和transferFrom,这两种拷贝方式在性能上比mmap的零拷贝方式还快。但是tranferTo和transferFrom 应对的需求是:经常需要从一个位置将文件传输到另外一个位置。FileChannel提供了transferTo()方法用来提高传输的效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值