Java面试系列之一

java运行环境

JDK,JRE,JVM概念
  • JDK:Java Development Kit,Java开发工具包,主要包括各种类库和工具,JRE.其中还有javac、java等,javac对.java文件进行编译成.class文件;java 对.class文件进行执行。
  • JRE:Java Runtime Environment,java程勋运行环境,包含JVM,Java类库class文件
  • JVM:虚拟机,编译执行相关java程序
配置编译器
  • JAVA_HOME:配置jdk文件路径,本机C:\developSoftware\developInstallSpace\jdk
  • PATH:配置jdk根目录即到bin目录C:\developSoftware\developInstallSpace\jdk\bin
  • CLASSPATH:可选配置,可以配置 “.
JVM及其工作原理

Java面试系列之一_第1张图片
JVM是一种用软件模拟出来的计算机,它用于执行Java程序,有一套非常严格的技术规范,是Java跨平台特性的依赖基础。Java虚拟机有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统。

jar以及常用jar命令

利用JDK的bin目录下的jar命令可以完成Java程序的打包,一般需要包含程序所需class文件、配置文件、和mainfest.mf文件。其中c、v和f这3个命令参数最为多见

  • 创建jar包:jar cf hello.jar helloWorld.class
  • 创建并显示打包过程:jar cvf hello.jar helloWorld.class
  • 显示jar包中的类容:jar tvf hello.jar
  • 解压jar包:jar xvf hello.jar
  • jar中添加文件: jar uf hello.jar helloWorld.java
java数据类型

基本数据类型和引用数据类型。最大的区别在于,引用数据类型存放的是数据所在地址,而基本数据类型则是直接存放数据的值。

  • 基本数据类型
基本类型 byte short int long float double char boolean
包装类型 Byte Short Integer Long Float Double character Boolean
所占字符 1 2 4 8 4 8 2 1
范围 -27~27 -215~215 -231~231 -263~263 单精度 双精度 字符 true或false
  • 引用数据类型
Java中的equal和==的区别

equal和“”两者均为表示相等的意思,但是它们相等的含义却有所区别:
”运用在基本数据类型的时候,通过比较它们实际的值来判断是否相同;而用于比较引用类型的时候,则是比较两个引用的地址是否相等,也就是是否指向同一个对象。
equal方法是java.lang.Object的方法,也就是所有的Java类都会有的方法。它可以被程序员覆盖重新,通过自定义的方式来判定两个对象是否相等。对于字符串java.lang.String类来说,它的equal方法用来比较字符串的字符序列是否完全相等。

Java中类和对象的区别

Java的类通过class关键字进行定义,它代表了一种抽象的集合,例如,学生类,动物类等,在它的里面可以定义各种属性和方法,它们代表了每个类实例的特定的数据和作用。Java虚拟机对类只加载一次,对它的静态成员也只加载一次。
对象,指的是某一个特定抽象的实例,它属于某一种类型,也就是对象是通过类来创建的。它必须从属于某一个类,通过new关键字吉星创建,它代表一个特定类型的实例,对象可以被多次创建。简而言之,类是一种抽象,而对象是类的实现。

Java的特性
  • 封装:封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。
  • 继承:Java采用的是单继承制,使用extend关键字。通过继承以后,子类就拥有了父类除开私有成员以外的所有成员,从而达到代码重用的目的。在集成过程中,可以通过方法的覆盖来实现多态,让子类拥有自己独特的方法实现方式。
  • 多态:“多态”一词按照字面意思来理解为“多种形式,多种状态”。它的本质是,发送消息给某个对象,让该对象自行决定响应何种行为。通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。
  • 抽象:抽象是指对于某一个对象,不能使用具体的语言进行描述。例如,动物类
Java中静态成员的特点

类的静态成员是通过static关键字修饰的成员,主要包括:静态成员变量、静态方法和静态代码块,它们具有一下一些特点:

  • 在类加载的时候,就进行创建和初始化或执行代码。
  • 它们对于一个类来说,都只有一份。
  • 类的所有实例都可以访问到它们。
Java中接口和抽象类的区别

抽象类是一种功能不全的类,接口只是一个抽象方法声明和静态不能被修改的数据的集合,两者都不能被实例化。从某种意义上说,接口是一种特殊形式的抽象类,在Java语言中,抽象类表示一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

Java中内部类

内部类根据定义的情况可以分为以下4种。
(1)静态内部类:它相当于外部类的静态成员一样,使用static修饰的内部类,它隶属于外部类,使用起来相当于一个独立的外部类。
(2)成员内部类:它相当于外部类普通的成员一样,隶属于外部类的具体对象,在定义它的时候,需要先创建外部类对象,再创建它的实例。
(3)局部内部类:它定义在一个方法的方法体中,它往往仅作为方法短暂的使用,只能访问用final修饰的局部变量。
(4)匿名内部类:它也定义在方法体中,但是它没有一个具体的名字,具有非常大的灵活性,工作本质与局部内部类类似。

Java中private、protected、public和default的区别

这些都是访问控制符,主要区别如下:
(1)public:可以被所有其他类访问。
(2)private:只能被自身访问和修改。
(3)protected:自身,子类及同一个包中类可以访问。
(4)default(默认):同一个包中的类可以访问,声明时没有加修饰符,认为是default。

Java中字符串对象池的作用

从Java5.0开始,Java虚拟机启动时会实例化9个对象池,存储8种基本类型的包装类对象和String对象。对象池的存在是为了避免频繁的创建和销毁对象而影响系统性能。当JVM在运行用双引号引起来一个字符串的代码时,会到String的对象池中去检查是否有一个字符序列相同的对象。如果有,就取现成的对象,如果没有,则在对象池中创建一个对象,并返回。

Java中的数组以及new Object[5]是否创建了5个对象

Java的数组本质上是一个类,该类还保存了数据类型的信息。该类通过成员变量的形式来保存数据,并且通过[]符号,使用下标来访问这些数据。new Object[5]其实是创建了一个数组实例,长度为5,每个元素的值均为null,并没有创建5个Object对象,如果是基本类型初始值为0。如果需要创建5个Object对象,则需要为每个数组元素分别指定。

package com.cy;
public class ArrayTest001 {
    public static void main(String[] args) {
        //数组赋值的两种方式
        //1.声明时进行赋值
        Object[] objects = new Object[]{111,222,333,444,555};
        //2.先声明再每个数组元素赋值
        Object[] objArr = new Object[5];
        objArr[0] = "关羽";
        objArr[1] = 1234;
        objArr[2] = 3.1415;
        objArr[3] = new student("刘备",30,"男");
        objArr[4] = new TestInterface() {
            @Override
            public String getStringMethod() {
                return "这是通过接口对象创建对象!!";
            }
            @Override
            public String toString() {
                return getStringMethod();
            }
        };

        for(int i = 0;i
Java中拷贝数组的数据

使用System.arraycopy()方法,或者创建一个新的数组实例,然后把数据–装填进去,不能用=赋值运算符。

Java中的集合
graph LR
A[Collection] -->B[Set]
A -->C[List]
B -->D[SortedSet]
E[Map] -->F[SortedMap]

集合是用来也只能存储其他对象的对象,代表了一种底层结构,用于扩展数组的功能。集合框架由一系列的接口和实现类组成,基本包括:列表(List)、集合(Set)、映射(Map)等,它们大多具有可迭代性和可比较性。

  • 列表(List):有序存放,允许重复,可以存放不同类型的对象;
  • 集合(Set):无序存放,不允许重复,可存放不同类型的对象;
  • SortedSet:排好序列的Set;
  • 映射(Map):键值对,包含key,value
  • SortMap:排好序列的Map
package com.cy;

import java.util.*;
import java.util.stream.Collectors;

public class CollctionTest001 {
    public static void main(String[] args) {
        Integer comInt = new Integer("123");
        Integer comInt1 = new Integer("456");
        //comInt小于comInt1返回-1
        System.out.println(comInt.compareTo(comInt1));
        //comInt1大于comInt返回1
        System.out.println(comInt1.compareTo(comInt));
        //相等返回0
        System.out.println(comInt1.compareTo(comInt1));
        /**
         * 从小到大的比较器,o1 > o2 返回 1,o1 < o2 返回 -1,o1 = o2 返回 0
         * 从大到小的比较器,o1 > o2 返回 -1,o1 < o2 返回 1,o1 = o2 返回 0
         */
        Comparator comparator = new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1 > o2) {
                    return -1;
                }else{
                    return 1;
                }
            }
        };
        //TreeSet默认是从小到大排序,
        Set integers = new TreeSet<>(comparator);
        integers.add(111);
        integers.add(444);
        integers.add(555);
        integers.add(333);
        integers.add(100);
        for (Integer integer : integers) {
            System.out.println(integer);
        }

        Map treeMap = new TreeMap<>();
        treeMap.put(5,111);
        treeMap.put(4,555);
        treeMap.put(3,222);
        treeMap.put(2,444);
        treeMap.put(1,333);

        //遍历方式1:利用entry进行遍历
        for (Map.Entry map : treeMap.entrySet()){
            System.out.println(map.getKey()+":"+map.getValue());
        }
        //遍历方式2:利用key进行遍历
        for (Integer key : treeMap.keySet()){
            System.out.println(key+":"+treeMap.get(key));
        }
        //遍历方式3:利用迭代器进行遍历
        Iterator> treeIter = treeMap.entrySet().iterator();
        while (treeIter.hasNext()){
            System.out.println(treeIter.next());
        }
        //遍历方式4:利用value进行遍历
        for (Integer i : treeMap.values()){
            System.out.println(i);
        }

        //treeMap根据value进行排序
        ArrayList> sortList = new ArrayList<>(treeMap.entrySet());
        Collections.sort(sortList, new Comparator>() {
            @Override
            public int compare(Map.Entry o1, Map.Entry o2) {
                if (o1.getValue() > o2.getValue()){
                    return 1;
                }else if(o1.getValue() == o2.getValue()){
                    return 0;
                }else{
                    return -1;
                }
            }
        });
        //根据value从小到大进行排序
        for (Map.Entry entry :sortList) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }
    }
}

Java中的迭代器与比较器

迭代器,提供一种访问一个集合对象中各个元素的途径,同时又不需要暴露该对象的内部细节。Java通过提供Iterable和Iterator两个接口来实现集合类的迭代性。迭代器主要的用法就是,首先用hasNext()作为循环条件;再用next()方法得到每一个元素;最后再进行相关操作。

List list = new ArrayList();
list.add(1);list.add(2);list.add(3);
Iterator listIter = list.iterator()
while(listIter.hasNext()){
    listIter.next();
}

比较器是把集合或数组的元素强行按照指定方式进行排序的对象,它是实现了Comparator接口类的实例。如果一个集合元素的类型是可比较的(实现了Comparable接口),那么它就具有了默认的排序方法,比较器则是强行改变它默认的比较方式来进行排序。或者有的集合元素不可比较(没有实现Comparable接口),则可用比较器来实现动态的排序。

Comparator comp = new Comparator(){
    public int compara(Integer o1,Integer o2){
        if(o1 > o2){
            return 1;
        }else if(o1 == o2){
            return 0;
        }else{
            return -1;
        }
    }
}
Java中vector与ArrayList,以及HashMap与HashTable的区别

Vetor是线程安全的,因为它的操作元素的方法都是同步方法,而ArrayList则不是。开发过程中应该根据需要进行选择,如果需要保证线程安全的地方则需要Vetor,而不必要的时候使用ArrayList效率会高一些。

HashMap和HashTable的区别主要有以下几个方面:

  • HashTable的方法是同步的,HashMap不能同步。
  • HashTable不允许null值(Key和Value都不可以),HashMap允许null值(Key和Value都可以)。
  • HashTable有一个contains()方法,功能和containsValue()功能一样。
  • HashTable使用Enumeration,HashMap使用Iterator进行遍历。
  • HashTable中hash数组的初始化大小及增长方式不同。
  • 哈希值的使用不同,HashTable直接使用对象hashCode,而HashMap会重新计算hash值。
Java数据集合使用foreach循环需满足条件

数组或者实现了Iterable接口的类实例,都是可以使用foreach循环的。

Java中的哈希运算过程

HashMap内部,使用 Entry[] 数组存放数据
数组默认初始容量 16
放满后容量翻倍+2

  1. key.hashCode() 获得键的哈希值
  2. 用哈希值和数组长度,运算产生一个下标值 i
  3. 新建Entry 对象来封装键值对数据
  4. 将Entry对象,放在 i 位置
    • 如果是空位置,直接放入;
    • 有数据,依次equals()比较是否相等,找到相等的,覆盖旧值,没有相等的,链表连接在一起
  5. 负载率、加载因子 0.75,如果容量超过当前容量*0.75
    新建翻倍容量的新数组,所有数据,重新执行哈希运算,放入新数组
  6. jdk1.8链表长度到8,转成红黑树,树上的数据减少到6,转回成链表
java中的IO流
  • Java提供了java.io.File类对目录和文件进行操作。主要的操作方法包括:路径字符串构造方法、isDirectory()、isFile()、createNewFile()、list()、getName()、delete()、mkdir()、mkdirs()、exists()
  • 字节流与字符流
    • 字节流处理的是计算机最基本的单位byte,它可以处理任何数据格式的数据。主要的操作对象就是byte数组,通过read()和write()方法把byte数组中的数据写入或读出。
    • 字符流是由字节流包装而来,它的输入和输出 流类型包括StringReader和StringWriter、BufferedReader和BufferedWriter。
package com.cy.IO;
import java.io.*;
/**
 * 实现文件夹的复制
 * 1.获取文件夹目录
 * 2.获取目标文件目录
 * 3.实现移动文件夹方法
 */
public class FileTest001 {
    public static void main(String[] args) {
        String fromPath = "C:\\test";
        String toPath = "C:\\test1";
        String fromFile = "C:\\810wangcheng\\browser\\前端书籍\\CSS3 实战[撩撩前端].pdf";
        String toFile = "C:\\test1\\CSS3 实战[撩撩前端].pdf";
        moveFileDir(fromPath,toPath);
        copyFileByBuffIo(fromFile,toFile);
    }

    private static void copyFileByBuffIo(String fromFile, String toFile) {
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        File fromFi = new File(fromFile);
        File toFi = new File(toFile);
        if (!fromFi.isFile()){
            throw new RuntimeException("来源不是一个文件");
        }

        try {
            bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(fromFile),"utf-8"));
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(toFi),"utf-8"));
            String data = null; //接收读取的数据
            while((data = bufferedReader.readLine()) != null){
                bufferedWriter.write(data);
            }
            System.out.println("完成文件复制!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedWriter != null){
                    bufferedWriter.close();
                }
                if (bufferedReader != null){
                    bufferedReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 1.首先判断是文件还是文件夹
     * 2.如果是文件进行io读写
     * 3.如果是文件夹获取所有文件,递归调用
     * @param fromPath
     * @param toPath
     */
    private static void moveFileDir(String fromPath, String toPath) {
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        File fromFile = new File(fromPath);
        if (!fromFile.exists()){
            throw new RuntimeException("要复制的文件不存在!");
        }
        //假如是第一层下面的文件
        try {
            if (fromFile.isFile()){
                File toFileDir = new File(toPath);
                if (!toFileDir.exists()){
                    toFileDir.mkdirs(); //逐层创建文件夹
                }
                byte[] data = new byte[1024];//存放读取数据
                int len = 0; //记录读取长度
                inputStream = new FileInputStream(fromFile);
                outputStream = new FileOutputStream(new File(toPath+"\\"+fromFile.getName()));
                while ((len = inputStream.read(data)) != -1){//循环读取文件
                    outputStream.write(data,0,len); //读出文件
                }
            }else{
                //如果是文件夹,递归调用
                String parentDir = fromFile.getPath().substring(fromFile.getPath().lastIndexOf("\\"));
                File[] files = fromFile.listFiles();
                for (File file : files) {
                    moveFileDir(file.getAbsolutePath(),toPath+parentDir);
                }
            }
            System.out.println("文件复制完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null){
                    outputStream.close();
                }
                if (inputStream != null){
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Java中的序列化

序列化本质上就是把对象内存中的数据按照一定的规则,变成一系列的字节数据,然后再把这些字节数据写入到流中。而反序列化的过程相反,先读取字节数据,然后再重新组装成Java对象。所有需要进行序列化的类,都必须实现Serializable接口,必要时还需要提供静态的常量serialVersionUID.

package com.cy.serializable;

import java.io.*;

class Student implements Serializable{
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private String gender;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}
public class SerialTest001{
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("刘备");
        student.setAge(30);
        student.setGender("男");
        ObjectOutputStream outputStream = null;
        ObjectInputStream inputStream = null;
        try {
            //创建一个对象输出流
            outputStream = new ObjectOutputStream(new FileOutputStream("c:/test/obj.dat"));
            outputStream.writeObject(student);
            outputStream.close();
            //创建一个对象输入流
            inputStream = new ObjectInputStream(new FileInputStream("c:/test/obj.dat"));
            Object obj = inputStream.readObject();
            Student stu = (Student) obj;
            System.out.println(stu);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(service,java,面试)