美文网首页Java 杂谈JAVA开发互联网科技
Java面试系列 — 基础篇(三)

Java面试系列 — 基础篇(三)

作者: 奋斗的蛐蛐 | 来源:发表于2018-07-13 19:30 被阅读83次

    先整理出一批面试笔试面试题。后续将继续更新,如果本文中出现问题,请及时与蛐蛐联系,蛐蛐马上继续修改,后续也会同步更新。

    流的原理

    • 在Java程序中,对于数据的输入/输出操作以“流” (stream) 方式进行;
    • SDK提供了各种各样的“流”类,用以获取不同种类的数据;程序中通过标准的方法输入或输出数据。
    • Java的流类型一般位于java.io包中

    备注: 输入输出是相对于程序而言,而不是相对于源和目标而言

    流的分类

    • 节点流:可以直接从数据源或目的地读写数据。
    • 处理流(包装流):不直接连接到数据源或目的地,是其他流进行封装。目的主要是简化操作和提高性能

    流的方向

    • 输入流:数据源到程序(InputStream、Reader读进来)
    • 输出流:程序到目的地(OutPutStream、Writer写出去)

    处理数据单元:

    • 字节流:按照字节读取数据(InputStream、OutputStream)
    • 字符流:按照字符读取数据(Reader、Writer)

    输入流和输出流联系和区别,节点流和处理流联系和区别

    • 首先,你要明白什么是“流”。直观地讲,流就像管道一样,在程序和文件之间,输入输出的方向是针对程序而言,向程序中读入东西,就是输入流,从程序中向外读东西,就是输出流。输入流是得到数据,输出流是输出数据。
    • 而节点流,处理流是流的另一种划分,按照功能不同进行的划分。节点流,可以从或向一个特定的地方(节点)读写数据。处理流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

    字符流字节流联系区别;什么时候使用字节流和字符流?

    • 字符流和字节流是流的一种划分,按处理照流的数据单位进行的划分。两类都分为输入和输出操作。在字节流中输出数据主要是使用OutputStream完成,输入使的是InputStream,在字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成。这四个都是抽象类。字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的 但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的编码来处理,也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联的。

    列举常用字节输入流和输出流并说明其特点,至少5对。

    • FileInputStream 从文件系统中的某个文件中获得输入字节。
    • ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read方法要提供的下一个字节。
    • FilterInputStream包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。FilterInputStream类本身只是简单地重写那些将所有请求传递给所包含输入流的 InputStream的所有方法。FilterInputStream 的子类可进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。
    • ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
    • ObjectOutputStream和ObjectInputStream分别与FileOutputStream和FileInputStream一起使用时,可以为应用程序提供对对象图形的持久存储
    • ObjectInputStream用于恢复那些以前序列化的对象。其他用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。
    • StringBufferInputStream此类允许应用程序创建输入流,在该流中读取的字节由字符串内容提供。应用程序还可以使用ByteArrayInputStream从byte数组中读取字节。只有字符串中每个字符的低八位可以由此类使用。
    • ByteArrayOutputStream此类实现了一个输出流,其中的数据被写入一个 byte数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
    • FileOutputStream文件输出流是用于将数据写入 File 或 FileDescriptor的输出流。文件是否可用或能否可以被创建取决于基础平台。特别是某些平台一次只允许一个FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。
      FilterOutputStream类是过滤输出流的所有类的超类。这些流位于已存在的输出流(基础输出流)之上,它们将已存在的输出流作为其基本数据接收器,但可能直接传输数据或提供一些额外的功能。FilterOutputStream类本身只是简单地重写那些将所有请求传递给所包含输出流的OutputStream的所有方法。FilterOutputStream的子类可进一步地重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。
    • ObjectOutputStream将Java对象的基本数据类型和图形写入 OutputStream。可以使用ObjectInputStream读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
    • PipedOutputStream可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。通常,数据由某个线程写入 PipedOutputStream对象,并由其他线程从连接的PipedInputStream 读取。不建议对这两个对象尝试使用单个线程,因为这样可能会造成该线程死锁。如果某个线程正从连接的管道输入流中读取数据字节,但该线程不再处于活动状态,则该管道被视为处于毁坏状态。

    说明缓冲流的优点和原理

    • 不带缓冲的流的工作原理:它读取到一个字节/字符,就向用户指定的路径写出去,读一个写一个,所以就慢了。带缓冲的流的工作原理:读取到一个字节/字符,先不输出,等凑足了缓冲的最大容量后一次性写出去,从而提高了工作效率
    • 优点:减少对硬盘的读取次数,降低对硬盘的损耗。

    序列化的定义、实现和注意事项

    想把一个对象写在硬盘上或者网络上,对其进行序列化,把他序列化成为一个字节流。

    实现和注意事项

    1. 实现接口Serializable,Serializable接口中没有任何的方法,实现该接口的类不需要实现额外的方法。
    2. 如果对象中的某个属性是对象类型,必须也实现Serializable接口才可以
    3. 序列化对静态变量无效
    4. 如果不希望某个属性参与序列化,不是将其static,而是transient
    5. 串行化保存的只是变量的值,对于变量的任何修饰符,都不能保存
    6. 序列化版本不兼容

    使用IO流完成文件夹复制(结合递归)

    public class DemoTest {
    
        public static void main(String[] args) {
            try {
                copyDirectiory("d:/test", "d:/test");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 复制单个文件
         *
         * @param sourceFile 源文件
         * @param targetFile 目标文件
         * @throws IOException
         */
        private static void copyFile(File sourceFile, File targetFile) throws IOException {
            BufferedInputStream inBuff = null;
            BufferedOutputStream outBuff = null;
            try {
                // 新建文件输入流
                inBuff = new BufferedInputStream(new FileInputStream(sourceFile));
                // 新建文件输出流
                outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));
                // 缓冲数组
                byte[] b = new byte[1024 * 5];
                int len;
                while ((len = inBuff.read(b)) != -1) {
                    outBuff.write(b, 0, len);
                }
                // 刷新此缓冲的输出流
                outBuff.flush();
            } finally {
                // 关闭流
                if (inBuff != null)
                    inBuff.close();
                if (outBuff != null)
                    outBuff.close();
            }
        }
    
        /**
         * 复制目录
         *
         * @param sourceDir 源目录
         * @param targetDir 目标目录
         * @throws IOException
         */
        private static void copyDirectiory(String sourceDir, String targetDir) throws IOException {
            // 检查源目录
            File fSourceDir = new File(sourceDir);
            if (!fSourceDir.exists() || !fSourceDir.isDirectory()) {
                return;
            }
            //检查目标目录,如不存在则创建
            File fTargetDir = new File(targetDir);
            if (!fTargetDir.exists()) {
                fTargetDir.mkdirs();
            }
            // 遍历源目录下的文件或目录
            File[] file = fSourceDir.listFiles();
            for (int i = 0; i < file.length; i++) {
                if (file[i].isFile()) {
                    // 源文件
                    File sourceFile = file[i];
                    // 目标文件
                    File targetFile = new File(fTargetDir, file[i].getName());
                    copyFile(sourceFile, targetFile);
                }
                //递归复制子目录
                if (file[i].isDirectory()) {
                    // 准备复制的源文件夹
                    String subSourceDir = sourceDir + File.separator + file[i].getName();
                    // 准备复制的目标文件夹
                    String subTargetDir = targetDir + File.separator + file[i].getName();
                    // 复制子目录
                    copyDirectiory(subSourceDir, subTargetDir);
                }
            }
        }
    }
    

    对象序列化 (Serialization)

    • 将Java对象转换成字节序列(IO字节流)
    • 对象的序列化主要有两种用途:
      • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件
      • 在网络上传送对象的字节序列。

    对象序列化的条件

    • 只有实现了Serializable接口的类的对象才可以被序列化。Serializable接口中没有任何的方法,实现该接口的类不需要实现额外的方法。
    • 如果对象的属性是对象,属性对应类也必须实现Serializable接口

    如何实现序列化(序列化流)

    1. 创建ObjectOutputStream对象
    2. 调用writeObject()输出对象
    Class clazz = new Class();
    OutputStream os = new FileOutputStream(new File("d:/test.txt"));
    ObjectOutputStream oos = new ObjectOutputStream(os);
    oos.writeObject(clazz);
    oos.close();
    

    如何实现反序列化(序列化流)

    1. 创建ObjectInputStream对象
    2. 调用readObject()读取对象
    InputStream is = new FileInputStream(new File("d:/java6.txt"));
    ObjectInputStream ois = new ObjectInputStream(is);
    Class clazz = (Class)ois.readObject();
    System.out.println(clazz.getMethod1()+"  "+clazz.getMethod2());
    

    JavaIO体系

    分类 字节输入流 字节输出流 字符输入流 字符数出流
    抽象基类 InputStream OutputStream Reader Writer
    文件 FileInputStream FileOutputStream FileReader FileWriter
    数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
    管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
    字符串 StringReader StringWriter
    缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
    转换流 InputStreamReader OutputStreamWriter
    对象流 ObjectInputStream ObjectOutputStream
    抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter
    打印流 PrintStream PrintWriter
    推回输入流 PushbackInputStream PushbackReader
    特殊流 DataInputStream DataOutputStream

    final的使用

    • final修饰变量
      • 使用final修饰变量,变量变成常量;
      • 只能赋值一次,可以是声明final常量时赋值,也可以声明后赋值一次
    • final修饰方法
      • 该方法不能被子类重写
    • final修饰类
      • 该类不能有子类
      • 如果一个类是final,不能有子类,自然也就没有方法的重写,所有其中的方法可以不写final
      • 如果一个类是final,变量的final不能省略

    short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?

    对于
    short s1 = 1; s1 = s1+1;
    由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。而short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换

    switch中能否使用string做参数

    在idk 1.7之前,switch只能支持byte, short, char, int或者其对应的封装类以及Enum类型。从idk 1.7之后switch开始支持String

    Java反射技术主要实现类有哪些,作用分别是什么?

    在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中

    1. Class类:代表一个类
    2. Field 类:代表类的成员变量(属性)
    3. Method类:代表类的成员方法
    4. Constructor 类:代表类的构造方法
    5. Array类:提供了动态创建数组,以及访问数组的元素的静态方法

    反射的应用场合

    在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息

    反射的作用

    • 通过反射可以使程序代码访问装载到JVM 中的类的内部信息
    • 获取已装载类的属性信息
    • 获取已装载类的方法
    • 获取已装载类的构造方法信息

    使用反射技术创建对象

    1. 通过Class的newInstance()方法

    • 该方法要求该Class对象的对应类有无参构造方法
    • 执行newInstance()实际上就是执行无参构造方法来创建该类的实例
    Class clazz=Class.forName("com.fddqq.reflection.Bean");
    Object obj=clazz.newInstance();
    
    //相当于执行语句:
    Bean bean = new Bean();
    

    2. 通过Constructor的newInstance()方法

    • 先使用Class对象获取指定的Constructor对象
    • 再调用Constructor对象的newInstance()创建Class对象对应类的对象
    • 通过该方法可选择使用指定构造方法来创建对象
    Class clazz=Class.forName("com.fddqq.reflection.Bean");
    Constructor cons = clazz.getConstructor(String.class,
    int.class, float.class );
    Object obj = cons.newInstance( "lkl", 32, 56.5f );
    
    //相当于执行语句:
    Bean bean=new Bean("lkl",32,56.5f);
    
    
    obj = clazz.getConstructor().newInstance();
    //相当于执行语句:
    Bean bean = new Bean();
    
    

    反射技术优缺点

    反射提高了Java程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类

    反射是其它一些常用语言,如C、C++、Fortran 或者Pascal等都不具备的

    Java反射技术应用领域很广,如软件测试、 EJB、JavaBean等

    许多流行的开源框架例如Struts、Hibernate、Spring在实现过程中都采用了该技术

    反射的缺点

    性能问题
    使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。

    使用反射会模糊程序内部逻辑
    程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。

    相关文章

      网友评论

      本文标题:Java面试系列 — 基础篇(三)

      本文链接:https://www.haomeiwen.com/subject/tlnjpftx.html