java 小记

好记性不如烂笔头。

 

 

Java语言中的数据类型:基本数据类型(1+7=1+5+2种)+ 引用类型(对象)

基本数据类型:【byte、short、int、long、float、double、char】、boolean

引用类型:类、接口、数组。如基本类型的包装类、其他各种对象类型

基本类型中数值类型间可相互转换,表示范围小的数值或变量可直接赋给另一个表示范围大的变量,会自动转换,反之会报错需要开发者强转:

 

 

 

 

1、获取web项目根目录的绝对路径

request.getContextPath()  获取项目名称,如    /BiYeSheJi

getServletContext().getRealPath("/")  获取项目根目录,如  C:\install files\programing software\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\BiYeSheJi\

request.getRealPath("");   //D:\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\zhx-web\

strDirectory="files" ,   request.getRealPath("//WEB-INF//" + strDirectory + "//");  //D:\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\zhx-web\WEB-INF\files\

 

获取放.class文件的目录的绝对路径:

(即Maven工程的classes绝对路径 或 非Maven工程的bin绝对路径,当打包成Jar运行时获取到的是Jar的绝对路径 /.../xxx.jar)

 1         // 获取类所在绝对路径
 2         java.net.URL url = Main_Other.class.getProtectionDomain().getCodeSource().getLocation();
 3         String filePath = null;
 4         try {
 5             filePath = url.getPath();//被用UTF-8编码了
 6             filePath = java.net.URLDecoder.decode(filePath, "utf-8");
 7             System.out.println(filePath);
 8         } catch (Exception e) {
 9             e.printStackTrace();
10         }

 

由上,可以获取程序的父目录名字:

(在Eclipse中运行时得到放置.class文件的目录的绝对路径[bin或Maven的classes],打包成jar时获得jar程序(无论是Maven还是普通程序)的所在目录的绝对路径):

1         String rootPath = Test.class.getProtectionDomain().getCodeSource().getLocation().getPath();
2         rootPath = rootPath.substring(0, rootPath.lastIndexOf("/") + 1);
3         System.out.println(rootPath);

其应用是可以以 rootPath+"/"+config.properties 指定路径来读取配置文件,这样开发时配置文件放在工程里,打包时放在程序所在目录,而不用更改配置文件路径。

注:在Spring [Boot]中则可以通过 ResourceUtils.getURL("classpath:").getPath() 获取到该路径。

 

 

2、前台URL中含有中文参数时,传给Java后台获取到的会是乱码,因为ISO-8859-1是Java中网络传输使用的标准字符集,String city=request.getParameter("city");得到的还是ISO-8859-1字符集,所以要转换一下,方法不止一种,如:city = new String(city.getBytes("ISO-8859-1"), "UTF-8");

获取到的中文参数存到后台数据库后,在数据库里都变成?的解决方法:在数据库连接的URL上加 ?useUnicode=true&characterEncoding=UTF-8 ,如 jdbc:mysql://172.20.4.183:3306/jpatest?useUnicode=true&characterEncoding=UTF-8 

 

3、20150315

Spring框架中提供一种模式,定义类的成员变量后不用写get或set方法就可以直接使用get或set,只要在变量前声明@Getter @Setter;

此外,对于进行@Autowired声明的类变量,不需要去new它,在Spring框架中会自动维护一个类对象。如果去new它也不会保存,但会产生一些影响,如如果该类在一个web接口中会被用到,则当接口请求频繁时会造成产生大量对象的恶果

 

获取类所在目录的绝对路径:

1         java.net.URL url = TestMain.class.getProtectionDomain().getCodeSource().getLocation();
2         String filePath = null;
3         try {
4             filePath = java.net.URLDecoder.decode(url.getPath(), "utf-8");
5             System.out.println(filePath);
6         } catch (Exception e) {
7             e.printStackTrace();
8         }

 

 

 

4、20150329

Java多线程实现方法:

 (1)写一个实现Runnable接口(及里面的run方法)的类,然后启动一个线程,此类作为线程的参数,再start

    class MyRunnable implements Runnable{
        @Override
        public void run() {
            // TODO Auto-generated method stub
        }
    }
    new Thread(new MyRunnable()).start();

 (2)写一个类,继承Thread类(实现run方法),然后new此类,再start

    class MyThread extends Thread {
        public void run() {
            System.out.println("fuck");
        }
    }
    (new MyThread()).start();

 

5、20150702  参考:http://www.cnblogs.com/qinqinmeiren/archive/2011/07/19/2151683.html

  Java 编译器默认为所有的 Java 程序导入了 JDK 的 java.lang 包中所有的类(import java.lang.*;),其中定义了一些常用类,如 System、String、Object、Math 等,因此我们可以直接使用这些类而不必显式导入。但是使用其他类必须先导入。

  不像 C/C++,Java 不支持无符号类型(unsigned)。

  float 类型有效数字最长为 8 位,有效数字长度包括了整数部分和小数部分;double 类型有效数字最长为 15 位。如2.123456789f+1f 结果为3.1234567f。

  Java也有格式化输出函数,例:System.out.printf("%d*%d=%2d ", i, j, i * j)。

  String变量被初始化后,长度和内容都不可变,每次对String的操作都会生成新的String对象,不仅效率低,而且耗费大量内存空间;对于String str = new String(“abc”),实际上创建了两个String对象,一个是”abc”对象,存储在常量空间中(常量池),一个是使用new关键字为对象str申请的空间。

            Integer i1 = new Integer(1);
            Integer i2 = new Integer(1);
            // i1,i2分别位于堆中不同的内存空间
            System.out.println(i1 == i2);// 输出false

            Integer i3 = 1;
            Integer i4 = 1;
            // i3,i4指向常量池中同一个内存空间
            System.out.println(i3 == i4);// 输出true

            // 很显然,i1,i3位于不同的内存空间
            System.out.println(i1 == i3);// 输出false

            // 值大于127时,不会从常量池中取对象
            Integer i5 = 128;
            Integer i6 = 128;
            System.out.println(i5 == i6);// 输出false

 6、String、StringBuffer、StringBuilder:前者效率最低,初始化后长度、内容不可变;后两者效率更高,StringBuilder类和StringBuffer类功能基本相似,方法也差不多,主要区别在于StringBuffer类的方法是多线程安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。

线程安全:

    • StringBuffer:线程安全
    • StringBuilder:线程不安全

速度:
一般情况下,速度从快到慢为 StringBuilder > StringBuffer > String,当然这是相对的,不是绝对的。

使用环境:

    • 操作少量的数据使用 String;
    • 单线程操作大量数据使用 StringBuilder;
    • 多线程操作大量数据使用 StringBuffer。

7、Java修饰符

  Java 通过修饰符来控制类、属性和方法的访问权限和其他功能,通常放在语句的最前端,分为访问修饰符和非访问修饰符。访问修饰符也叫访问控制符,是指能够控制类、成员变量、方法的使用权限的关键字。

总的来说,访问控制控制的是一个类是否可被 同一包内的其他类、是否可被其子类 访问。从集合论的角度来看,假设访问权限的空间为U、同包的类为A、子类为B,则A∩B表示同包下的子类、A-B表示同包下的非子类、U-A表示非同包下的类、等等。有四种情况:

关键字名称类内部本包子类外部包说明
public Y Y Y Y 对所有类可见,不管是不是同包、不管是不是子类。即U
protected Y Y Y N 对所有子类(同包或不同包均可)和同一包内的类可见。即A∪B
无关键字 Y Y N N 对本包内的类可见。即A
private Y N N N 对所有其他类不可见,只对自己可见。即∅

 public 最大访问权限,所有都可以访问。

protected 是针对于子类设计的,对于子类和本包来说,被protected修饰的父类的方法和属性就是public的,可以自由访问,没有限制,而对于外部的class,其权限就相当于private,限制访问。

default 方法只是对于本类和本包,如果子类和父类不再一个包中,则也是不可以访问的。

private 只允许本类访问。

 

 

java在编译的时候会进行访问控制的检查。需要注意的是,通过反射的手段,是可以访问任何包下任何类中的成员,例如,访问类的私有成员也是可能的。

8、Java变量类型及作用域

变量类型:前两者会自动初始化,第三个不会,须显式初始化。

类变量(或称静态变量、全局变量),属于类,通过类或实例访问;声明周期:随类的加载和卸载而产生和销毁

实例变量(或称成员变量),属于实例,通过实例访问;声明周期:随对象的创建和回收而产生和销毁

局部变量,在方法(类方法、实例方法、构造方法等)、代码块(静态块、实例块)内定义的变量;声明周期:随方法、构造器、代码块的执行而产生和销毁

  变量作用域:在Java中,变量的作用域分为四个级别:

类级(全局级变量或静态变量,需要使用static关键字修饰)

对象实例级(成员变量,实例化后才会分配内存空间,才能访问)

方法级(包括类方法、实例方法、构造方法,在方法内部定义的变量,就是局部变量)

块级(定义在一个块内部的变量,也是局部变量,变量的生存周期就是这个块)。

块可以是静态块或非静态块,还可以是在方法内的块。

块内部能够访问类级、实例级变量,如果块被包含在方法内部,它还可以访问方法级的变量。

方法内部除了能访问方法级的变量,还可以访问类级和实例级的变量。

方法级和块级的变量必须被显示地初始化,否则不能访问。

9、this关键字

   this 关键字用来表示当前对象本身,或当前类的一个实例,通过 this 可以调用本对象的所有方法和属性。

    • 构造方法中区分形参和类的成员变量:
          public String name;
          public int age;   
          public Demo(String name, int age){
                this.name = name;
                this.age = age;
            }
          
    • 构造方法重载时作为方法名初始化对象:
      public class Demo{
           public Demo(){
               this("博客园", 10);
           }
           public Demo(String name, int age){
               this.name = name;
               this.age = age;
           }
      }
    • 作为对象参数传递
      public class Demo{
          public static void main(String[] args){
              B b = new B(new A());
          }
      }
      class A{
          public A(){
              new B(this).print();  // 匿名对象
          }
          public void print(){
              System.out.println("Hello from A!");
          }
      }
      class B{
          A a;
          public B(A a){
              this.a = a;
          }
          public void print() {
              a.print();
              System.out.println("Hello from B!");
          }
      }
      View Code

10、构造方法不能被继承,要使用父类的构造方法,可以通过super关键字

11、重写(覆盖)和重载:

  前者子类继承父类,覆盖父类的方法,参数类型、个数、顺序、返回类型必须与父类一致,可通过super调用父类被覆盖的方法(父类被覆盖的方法还能用);

  后者在一个类中有多个同名方法,只要参数类型、个数、顺序至少一者不同即可;

  父类的一个方法只能被子类覆盖一次(否则一个类中有参数列表和返回类型都完全相同的方法会报错),而一个方法可以在所有的类中可以被重载多次。

  重写的方法不能比原方法抛出更多的异常;被重写的方法不能是final类型、不能为private、不能为static

12、面向对象的三大特性:封装、继承、多态。

  多态存在的三个必要条件:要有继承、要有重写、父类变量引用子类对象。

  多态可以理解为父类既可以指向父类实例,也可以指向其子类的实例,对于一个方法,既可以表现父类的行为,如果被子类继承则表现为子类的行为,从而有不同的表现形式。

  当使用多态方式调用方法时:

  • 首先检查父类中是否有该方法,如果没有,则编译错误;如果有,则检查子类是否覆盖了该方法。(可见,具有不同表现形式即多态是相对于被继承的方法来说的)
  • 如果子类覆盖了该方法,就调用子类的方法,否则调用父类方法。

13、多态性相关问题:

  (1)如何判断一个变量所实际引用的对象的类型 ? C++使用runtime-type information(RTTI),Java 使用 instanceof 操作符:如果变量引用的是当前类或它的子类的实例,instanceof 返回 true,否则返回 false。如下:

public final class Demo{
    public static void main(String[] args) {
        // 引用 People 类的实例
        People obj = new People();
        if(obj instanceof Object){
            System.out.println("我是一个对象");
        }
        if(obj instanceof People){
            System.out.println("我是人类");
        }
        if(obj instanceof Teacher){
            System.out.println("我是一名教师");
        }
        if(obj instanceof President){
            System.out.println("我是校长");
        }
        System.out.println("-----------");  // 分界线
       
        // 引用 Teacher 类的实例
        obj = new Teacher();
        if(obj instanceof Object){
            System.out.println("我是一个对象");
        }
        if(obj instanceof People){
            System.out.println("我是人类");
        }
        if(obj instanceof Teacher){
            System.out.println("我是一名教师");
        }
        if(obj instanceof President){
            System.out.println("我是校长");
        }
    }
}
class People{ }
class Teacher extends People{ }
class President extends Teacher{ }
View Code

  (2)对象类型转换

  这里所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,java 运行时将抛出 java.lang.ClassCastException 异常。子类向父类转换称为“向上转型”(如父类变量引用子类对象),将父类向子类转换称为“向下转型”(有些时候为了完成某些父类没有的功能,我们需要将向上转型后的子类对象再转成子类,调用子类的方法,这就是向下转型),向上转型会自动进行,向下转型的对象必须是当前引用类型的子类。

  不能直接将父类的对象强制转换为子类类型,只能将向上转型后的子类对象再次转换为子类类型。也就是说,子类对象必须向上转型后,才能再向下转型。因为向下转型存在风险,所以在接收到父类的一个引用时,请务必使用 instanceof 运算符来判断该对象是否是你所要的子类。示例如下:

public class Demo {
    public static void main(String args[]) {
        SuperClass superObj = new SuperClass();
        SonClass sonObj = new SonClass();
        // superObj 不是 SonClass 类的实例
        if(superObj instanceof SonClass){
            SonClass sonObj1 = (SonClass)superObj;
        }else{
            System.out.println("①不能转换");
        }
        superObj = sonObj;
        // superObj 是 SonClass 类的实例
        if(superObj instanceof SonClass){
            SonClass sonObj2 = (SonClass)superObj;
        }else{
            System.out.println("②不能转换");
        }
    }
}
class SuperClass{ }
class SonClass extends SuperClass{ }
View Code

 14、final

  在 Java 中,声明类、变量和方法时,可使用关键字 final 来修饰。final 所修饰的数据具有“终态”的特征,表示“最终的”意思。具体规定如下:

  • final 修饰的类不能被继承。
  • final 修饰的方法不能被子类重写。
  • final 修饰的变量(成员变量或局部变量)即成为常量,只能赋值一次。
    • final 修饰的成员变量必须在声明的同时赋值,如果在声明的时候没有赋值,那么只有 一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
    • final 修饰的局部变量可以只声明不赋值,然后再进行一次性的赋值。
    • 如果将引用类型(任何类的类型)的变量标记为 final,那么该变量不能指向任何其它对象。但可以改变对象的内容,因为只有引用本身是 final 的。

15、内部类

  在 Java 中,允许在一个类(或方法、语句块)的内部定义另一个类,称为内部类(Inner Class),有时也称为嵌套类(Nested Class)。内部类和外层封装它的类之间存在逻辑上的所属关系,一般只用在定义它的类或语句块之内,实现一些没有通用意义的功能逻辑,在外部引用它时必须给出完整的名称。参见 这 里

  内部类是 Java 1.1 的新增特性,看似增加了—些优美有趣,实属没必要的特性;内部类语法很复杂,严重破坏了良好的代码结构, 违背了Java要比C++更加简单的设计理念。

创建内部类的方法:

1 Outer outer = new Outer();
2 Outer.Inner inner = outer.new Inner(); //Inner是实例的内部类
3 Outer.StaticInner staInner = new Outer.StaticInner(); //StaticInner是静态内部类

 

16、抽象类和接口

  抽象类:

  抽象类是指包含零或多个抽象方法的类,类名和抽象方法均由abstract修饰,如 abstract class A{int a,b; abstract void getA(); int getB(){return b;}};

  抽象类可以有实现了的方法、可以有初始化或未初始化的成员变量,当没有抽象方法时,它和普通类类似,只是多了abstract修饰;

  抽象类不能用来创建实例(故不能有构造方法),只能用继承它的子类创建实例,但可以用抽象类定义引用变量并指向子类对象。

  接口:

  接口是特殊的抽象类,是若干常量和抽象方法的集合,接口名由interface修饰;

  接口不能用来创建实例(故不能有构造方法),只能用实现了该接口的类来创建实例,但可以用接口定义变量指向其实现类的对象;

  接口中声明的成员变量默认都是 public static final 的,由于是final,必须显示地初始化且实现类不能修改其值。在常量声明时可以省略这些修饰符;

  接口中只能定义抽象方法,这些方法默认为 public abstract 的,因而在声明方法时可以省略这些修饰符。试图在接口中定义实例变量、非抽象的实例方法及静态方法,都是非法的。

// 接口成员变量必须是public static void,可省略修饰符
// 接口方法必须是public abstract,可省略修饰符
// 上述两者修饰符可省略,在显示指定修饰符时,与上述一个修饰符相悖的都是非法的
interface SataHdd {
    // 连接线的数量
    public int connectLine; // 编译出错,connectLine被看做静态常量,必须显式初始化

    // 写数据
    protected void writeData(String data); // 编译出错,必须是public类型
    // 读数据

    public static String readData(){ //编译出错,接口中不能包含静态方法
        return "数据"; //编译出错,接口中只能包含抽象方法,
    }
}

class BBB implements SataHdd {
    public BBB() {
        this.connectLine = 33;// 错误,connectLine为public static final,值不能更改
    }

    public int getData() {
        return 0;
    }
}
View Code

 

17、JDK1.2后,Java对引用概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Soft Reference)、虚引用(Phantom Reference)。未被任一种引用引用的对象会被回收(这就是回收算法要干的活:如何确定没有被引用的对象、什么时候回收、被如何回收),被后三种引用引用的对象在一定条件下会被回收。

  • 强引用:只要对象被强引用引用,垃圾回收器就不会回收该对象。这类引用最常用,为类似“Object obj=new Object();”这样的引用。
  • 软引用:用来描述一些还有用但并非必需的对象。在系统将发生内存溢出异常前会把这类引用的对象进行回收。JDK1.2开始提供了SoftReference类实现软引用。
  • 弱引用:也用来描述非必需对象,但强度比软引用更弱一些。被弱引用引用的对象只能生产到下一次垃圾回收发生之前。JDK1.2开始提供了WeakReference类实现弱引用。
  • 虚引用:最弱,亦称为幽灵引用或幻影引用。一个对象是否有虚引用完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为对象设置虚引用的唯一目的是能在此对象呗垃圾收集器回收时收到一个系统通知。JDK1.2开始提供了PhantomReference类实现此引用。

18、Java Runtime.getRuntime.exec()的执行过程:首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用此新进程去执行外部命令,最后再退出这个新进程。若频繁执行此操作,系统CPU、内存消耗会很大。

19、

OpenJDK是Sun在2006年末把Java开源而形成的项目,其源码与Sun/Oracle JDK源码除了文件头的版权注释之外,其余代码基本相同。其基于正在研发的JDK7上产生,剥离JDK7新功能的代码产生第一个版本OpenJDK6 

64位虚拟机可以管理更多的内存,但Java程序运行在64位虚拟机上需要付出比较大的额外代价:

1、由于指针膨胀和各种数据类型对其补白的原因,运行于64位系统上的Java应用需要消耗更多的内存,通常比32位增耗10%-30%

2、64位虚拟机的运行速度比32位有15%左右的性能差异

目前大多数的企业还是选择虚拟集群等方式继续在32位虚拟机中进行部署。

随着硬件技术的发展,计算机终究会过渡到64位的时代,主流的虚拟机应用终究也会从32位过渡到64位,而虚拟机对64位的支持也将会进一步改善。

20、Java获取指定范围内的随机数

    public static int getRandom(int min, int max, boolean isMaxInclude) {
        return new Random().nextInt(max - min + (isMaxInclude ? 1 : 0)) + min;
    }

21、每个对象都有一个锁和一个等待队列,类对象也不例外。synchronized保护的是对象,对实例方法,保护的是当前实例对象this,对静态方法,保护的是类对象。synchronized静态方法和synchronized实例方法保护的是不同的对象,不同的两个线程,可以同时,一个执行synchronized静态方法,另一个执行synchronized实例方法。

22、

获取本地IP地址: InetAddress.getLocalHost().getHostAddress() 

23、Java.nio.channel.FileChannel的 transferTo  transferFrom 是零拷贝的两个函数

24、jdk http请求

  1 import java.io.BufferedReader;
  2 import java.io.IOException;
  3 import java.io.InputStreamReader;
  4 import java.io.PrintWriter;
  5 import java.io.UnsupportedEncodingException;
  6 import java.net.URL;
  7 import java.net.URLConnection;
  8 import java.util.List;
  9 import java.util.Map;
 10 
 11 public class HttpRequest {
 12     /**
 13      * 向指定URL发送GET方法的请求
 14      * 
 15      * @param url
 16      *            发送请求的URL
 17      * @param param
 18      *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
 19      * @return URL 所代表远程资源的响应结果
 20      */
 21     public static String sendGet(String url, String param) {
 22         String result = "";
 23         BufferedReader in = null;
 24         try {
 25             String urlNameString = url + "?" + param;
 26             URL realUrl = new URL(urlNameString);
 27             // 打开和URL之间的连接
 28             URLConnection connection = realUrl.openConnection();
 29             // 设置通用的请求属性
 30             connection.setRequestProperty("accept", "*/*");
 31             connection.addRequestProperty("content-Type",
 32                     "text/html;charset=utf-8");
 33             connection.setRequestProperty("connection", "Keep-Alive");
 34             connection.setRequestProperty("user-agent",
 35                     "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
 36             // 建立实际的连接
 37             connection.connect();
 38             // 获取所有响应头字段
 39             Map<String, List<String>> map = connection.getHeaderFields();
 40             // 遍历所有的响应头字段
 41             for (String key : map.keySet()) {
 42                 System.out.println(key + "--->" + map.get(key));
 43             }
 44             // 定义 BufferedReader输入流来读取URL的响应
 45             in = new BufferedReader(new InputStreamReader(
 46                     connection.getInputStream(), "UTF-8"));
 47             String line;
 48             while ((line = in.readLine()) != null) {
 49                 result += line;
 50             }
 51         } catch (Exception e) {
 52             System.out.println("发送GET请求出现异常!" + e);
 53             e.printStackTrace();
 54         }
 55         // 使用finally块来关闭输入流
 56         finally {
 57             try {
 58                 if (in != null) {
 59                     in.close();
 60                 }
 61             } catch (Exception e2) {
 62                 e2.printStackTrace();
 63             }
 64         }
 65         return result;
 66     }
 67 
 68     /**
 69      * 向指定 URL 发送POST方法的请求
 70      * 
 71      * @param url
 72      *            发送请求的 URL
 73      * @param param
 74      *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
 75      * @return 所代表远程资源的响应结果
 76      */
 77     public static String sendPost(String url, String param) {
 78         PrintWriter out = null;
 79         BufferedReader in = null;
 80         String result = "";
 81         try {
 82             URL realUrl = new URL(url);
 83             // 打开和URL之间的连接
 84             URLConnection conn = realUrl.openConnection();
 85             // 设置通用的请求属性
 86             conn.setRequestProperty("accept", "*/*");
 87             conn.setRequestProperty("connection", "Keep-Alive");
 88             conn.setRequestProperty("user-agent",
 89                     "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
 90             // 发送POST请求必须设置如下两行
 91             conn.setDoOutput(true);
 92             conn.setDoInput(true);
 93             // 获取URLConnection对象对应的输出流
 94             out = new PrintWriter(conn.getOutputStream());
 95             // 发送请求参数
 96             out.print(param);
 97             // flush输出流的缓冲
 98             out.flush();
 99             // 定义BufferedReader输入流来读取URL的响应
100             in = new BufferedReader(
101                     new InputStreamReader(conn.getInputStream()));
102             String line;
103             while ((line = in.readLine()) != null) {
104                 result += line;
105             }
106         } catch (Exception e) {
107             System.out.println("发送 POST 请求出现异常!" + e);
108             e.printStackTrace();
109         }
110         // 使用finally块来关闭输出流、输入流
111         finally {
112             try {
113                 if (out != null) {
114                     out.close();
115                 }
116                 if (in != null) {
117                     in.close();
118                 }
119             } catch (IOException ex) {
120                 ex.printStackTrace();
121             }
122         }
123         return result;
124     }
125 
126     public static String doRequest(String url, String param, boolean isGetMethod) {
127         if (isGetMethod) {// 发生GET请求
128             return HttpRequest.sendGet(url, param);
129         } else {// 发生POST请求
130             return HttpRequest.sendPost(url, param);
131         }
132     }
133 
134     public static void main(String[] args) throws UnsupportedEncodingException {
135         String url = "http://localhost:8080/zhx-web/test1/name="
136                 + java.net.URLEncoder.encode("小明", "utf-8");
137         System.out.println(url);
138         // url =
139         // "http://localhost:8080/zhx-web/test1/name=小明";//程序中按默认编码对中文进行url
140         // encode,这里是GBK;浏览器一般是utf-8
141         String param = "";
142 
143         url = "http://mu:8080/odp-web/route/add/";
144         param = "arraydata=255234";
145         
146         
147         url = "http://restapi.amap.com/v3/direction/driving";
148         param = "origin=116.45925,39.910031&destination=116.587922,40.081577&output=json&key=7b00cf949d5bee7805f96527129b7c29";
149         
150         String res = HttpRequest.doRequest(url, param, false);
151         System.out.println(new String(res.getBytes("gbk"),"utf-8"));
152     }
153 }
View Code

25、通过代码获取程序当前占用内存的大小

 1 //法1,通过Runtime.getRuntime()获取
 2 Runtime myRuntime = Runtime.getRuntime();
 3 myRuntime.maxMemory();//进程(JVM)可从OS申请的最大内存大小,也即程序运行时候的堆的最大可能的值。可通过-Xmx指定。
 4 myRuntime.totalMemory();//进程(JVM)当前已从OS申请的内存大小,也即程序当前堆的大小。可通过-Xms设置初始大小,运行过程中可能增加或减少,但不会小于-Xms指定的值。
 5 myRuntime.freeMemory();//进程(JVM)已从OS申请的内存中,还有未被使用。
 6 myRuntime.totalMemory()-myRuntime.freeMemory()即为进程真正使用的内存大小。
 7 
 8 //法2,通过ManagementFactory.getMemoryMXBean()获取
 9 MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
10 MemoryUsage usage = memorymbean.getHeapMemoryUsage();//每次获取下面的新值前都要调用此代码,而通过Runtime则不用
11 usage.getInit();//-Xms指定的大小
12 usage.getUsed();//即myRuntime.totalMemory()-myRuntime.freeMemory()
13 usage.getCommitted();//即myRuntime.totalMemory()
14 usage.getMax();//即myRuntime.maxMemory()

26、Java类变量(静态变量)的初始化方式:定义时初始化、静态代码块初始化

27、Java创建类的实例的方式:通过new、通过反射( Class.forName("类的完全限定名"); )、通过反序列化

28、Java泛型:http://blog.csdn.net/seu_calvin/article/details/52230032https://blog.csdn.net/s10461/article/details/53941091

Java泛型只在编译阶段有效,在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除。不过不会全部擦除,成功编译过后的class文件中仍会保留部分泛型信息。

 

Java 在编译时会在字节码里指令集之外的地方保留「部分」泛型信息
泛型接口、类、方法定义上的所有泛型、成员变量声明处的泛型「都会」被保留类型信息,「其它地方」的泛型信息都会被擦除。例如对于 Person类里的字段 private List<Long>ids 在编译后会保存Long类型信息。

 

总结:

定义。泛型中,类型参数的位置:

对于泛型类和泛型接口:在类或接口名后,如 public class Generator<T>

对于泛型方法:在方法返回值前面,如public <T> T next()

静态方法中的泛型。静态方法无法使用类上定义的泛型参数(why?因为泛型参数只在编译期有效);从而,静态方法要使用泛型的话自然自身得是个泛型方法。

泛型转换:泛型参数的具体类型实例不能互相转换,即使泛型的具体类型有继承关系,如List<Number> numbers=new List<Integer>() 会报编译错误。

泛型边界:

通配符:List<?> numbers=new List<Integer>() 则可以,这里问号表示通配符。

上下界:上下界的声明须与泛型类型声明放一起,如 public <T extends Number> T showKeyName(Generic<T> obj)正确,public <T> T showKeyName(Generic<T extends Number> obj)错误。public void showKeyValue1(Generic<? extends Number> obj)则可以。

泛型数组。不允许创建泛型数组:List<String>[] ls = new ArrayList<String>[10]; 会编译错误:"Cannot create a generic array of ArrayList<String>"

 更多示例可参阅:https://www.cnblogs.com/z-sm/p/6259859.html

 

 

29、Java char

在Java内部进行字符处理时,采用的都是Unicode,具体编码格式是UTF-16BE。

char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编号,用于表示那个Unicode编号对应的字符。由于固定占用两个字节,char只能表示Unicode编号在65536以内的字符,而不能表示超出范围的字符。那超出范围的字符怎么表示呢?使用两个char。

char的位运算可以看做就是对应整数的位运算,只是它是无符号数,也就是说,有符号右移>>和无符号右移>>>的结果是一样的。

 

30、Java可变长度参数

可变长参数的语法是在数据类型后面加三个点...,在函数内,可变长度参数可以看做就是数组来使用,可变长度参数必须是参数列表中的最后一个参数,一个函数也只能有一个可变长度的参数。

可变长度参数实际上会转换为数组参数,也就是说,函数声明max(int min, int... a)实际上会转换为 max(int min, int[] a),在main函数调用 max(0,2,4,5)的时候,实际上会转换为调用 max(0, new int[]{2,4,5}),使用可变长度参数主要是简化了代码书写。

31、创建数组时:

要么显式指定初始值,如 int[] arr = {1,2,3}; 、 int[] arr = new int[]{1,2,3}; 

要么显式指定长度(此时按默认值自动初始化),如 int[] arr = new int[3]; 

两者不能同时指定,如 int[] arr = new int[3]{1,2,3} 是编译报错。这样可以防止初始值的个数与指定的长度不一致。

32、数组是引用类型,所以与类、接口的对象一样,可以变量间直接赋值(实际上是改变变量的引用,被引用的对象的内容并没变)。

1         int[] aa = new int[] { 1, 2, 3 };
2         int[] ab = new int[] { 4, 5, 6, 7 };
3         int[] atmp;
4         System.out.println(aa.length + " " + ab.length);// 3 4
5         atmp = aa;
6         aa = ab;
7         ab = atmp;
8         System.out.println(aa.length + " " + ab.length);// 4 3

33、Java switch里参数须为表达式、case的值须为常量表达式。表达式的类型可为:int(及比它小的类型,byte、short、char,不能为long、float、double等)、enum、String(Java1.7后)。其实最终都是通过整数,char本质上是整数、enum有对应的整数、String通过hashCode转为整数。

 分支语句有 switch、if else、用于条件赋值的三元运算符等。

后两者编译后转换为 条件跳转/无条件跳转 语句;

前者如果分支数多采用条件跳转/无条件跳转需要进行很多词比较运算效率较低,可能会采用更高效的方式——跳转表,表的键为分支整数且有序、值为分支地址,因此能二叉查找,从而跳转效率更高。这里的键就是case 后的表达式值。

因此,如果分支数较多,采用 switch 比 if else 效率高。

34、用于String的“+”与“+=”是Java中仅有的两个重载过的操作符,除这两个外没有任何重载操作符,也不允许程序员重载任何操作符。

35、Java语言中的转义字符及正则语法中的转义字符在Java字符串中的表示。

ASCLL中的转义字符共有14个:

\o

空字符(NULL)

00H/0

\n

换行符(LF)

0AH/10

\r

回车符(CR)

0DH/13

\t

水平制表符(HT)

09H/9

\v

垂直制表(VT)

0B/11

\a

响铃(BEL)

07/7

\b

退格符(BS)

08H/8

\f

换页符(FF)

0CH/12

\’

单引号

27H/39

\”

双引号

22H/34

\\

反斜杠

5CH/92

\?

问号字符

3F/63

\ddd

任意字符

三位八进制

\xhh

任意字符

二位十六进制

在字符串或字符中只要出现反斜杠就 连同其后的一或多个字符 被认为是一个转义字符并尝试解析之,若不是合法的转义字符就会出错。在大多数语言中均如是,只不过可能不支持所有的转义字符

语言对转义字符的处理:

在Java中支持的只有11个: \0 \n \r \t \b \f \' \" \\ \ddd \xhh ,即在Java中与反斜杠搭配的只能有这11种情况,除此之外的都被认为是有误的。如 "\c" 或 '\c' 都会报错,即使我们本意不是想把前者中的 \c 当做转义字符而是想表示 \ 和 c 这两个字符,但由于语言看到了斜杠就会按转义字符去解析,所以对于前后者都会因解析不成转义字符而报错。故若想表示这两个字符而不报错,就要加以处理以让语言不把它们当做转义字符解析,方法是对斜杠转义(让语言把斜杠当做普通字符而非转义字符的开始)——即 “\\c" 。 

 36、关于传值、传址

所谓传值、传址,是指调用函数时传入的实参是 变量的内容(或称变量的值) 还是 变量的位置(或称变量的地址)。对于前者,在被调用函数里是没法改变实参变量的值的,而后者则可以。

对Java来说,只有传值这一方式,因此无论如何都没有办法在被调用的函数里改变实参变量的值。

根据参数类型的不同,传的“值”的含义也不同,具体来说:

1、若参数是基本数据类型,则传的为变量的值。如int、long等

2、若参数是对象类型,则传的为引用(即对象的地址)。此时在被调用函数里虽然没法改变实参变量的值(即让该实参变量指向其他对象),但却可通过该引用改变实参变量指向的对象的内容。

对于String、基本数据类型包装类如Integer 等immutable类型,虽然也为对象类型,但因为其没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待。可以认为是传值,因此没办法在函数里改变引用对象的内容。

示例:

 1 class Person {
 2     public String name = "LiMing";
 3 }
 4 class TestCase {
 5     public static void test(Person p, Integer count, String address) {
 6         p.name = "XiaoLi";
 7         p = null;
 8         count = 2;
 9         address = "Beijing";
10     }
11     public static void main(String[] args) {
12         Person person = new Person();
13         Integer count = 1;
14         String address = "Shanghai";
15         System.out.println(person.name + " " + (person == null) + " " + count + " " + address);// LiMing false 1 Shanghai
16         test(person, count, address);
17         System.out.println(person.name + " " + (person == null) + " " + count + " " + address);// XiaoLi false 1 Shanghai
18     }
19 }
View Code

Java对变量值的解析方式:

若变量是基本数据类型,则直接使用该值进行加减等操作;

若变量是对象类型,则把该值当做地址,需要一次据之进行一次寻址才能拿到要进行加减等操作的值。

对C来说,除了传值外,还有传址方式,因此能在被调用函数里改变实参变量的值。

1、若参数是基本数据类型,则为传值,如int、long等

2、若参数是指针类型,且实参是一个指针变量,则在被调用函数里无法改变实参即该变量的内容但可以改变该变量指向的对象的内容。这相当于上述Java里的引用情形。

特别地,若实参是形如  &a 这样取值运算符加变量的形式,则实参的值会被改变,此即传址。实际上,对变量取址就隐含得到了指向该变量的指针,所以函数里能够改变该指针指向的对象的内容即变量的值。

示例:如下所示,传指针变量a时调用前后指针变量a的内容不变但其指向的对象的内容变了;传&d2前后d2的内容变了。

 1 void test(int * p,int *q)
 2 {
 3     *p=10;
 4     *q=20;
 5 }
 6 int main()
 7 {
 8     int d1=1,d2=2;
 9     int *a=&d1;
10 
11     printf("%p\n",a);//000000000062FE1C
12     test(a,&d2);
13     printf("%p\n",a);//000000000062FE1C
14     printf("%d %d %d\n",*a,d1,d2);//10 10 20
15     
16     return 0;
17 }

 37、String.intern()

String.intern()是个Native方法,用于返回字符串在常量池中的“位置”。其作用在不同JDK上不同:

  • 在JDK1.7以前,其返回常量池中字符串与此字符串相同的String对象,若不存在则先将此字符串添加到常量池,然后返回常量池中的该字符串对象。(即将首次出现的字符串复制到常量池并返回常量池中的该字符串对象
  • 从JDK1.7开始,不再将首次出现的字符串复制到常量池,而是在常量池中记录首次出现的字符串的引用

示例:

 1         String s1 = new StringBuilder().append("hello ").append("there").toString();
 2         System.out.println(s1 == s1.intern());
 3 
 4         System.out.println();
 5         String s2 = new StringBuilder().append("hello ").append("there").toString();
 6         System.out.println(s2 == s2.intern());
 7 
 8         System.out.println();
 9         System.out.println(s1 == s2);
10         System.out.println(s1.intern() == s2.intern());
11         System.out.println(s1 == s2.intern());
12         System.out.println(s1.intern() == s2);
13 
14         System.out.println();
15         String s3 = new StringBuilder().append("ja").append("va").toString();
16         System.out.println(s3==s3.intern());//false
View Code

若在JDK1.7之前的虚拟机上执行,则结果为 false, false, false, true, false, false, false。   此时s1为堆对象,s1.intern()为常量池中的对象,故不等,其他同理。

若在JDK1.7及之后的虚拟机上执行,则结果为 true, false, false, true, true, false, false。 此时s1为堆对象,s1.intern()亦为堆对象(因为常量池中保存的是s1对象的引用),故相等,即s1.intern()和s2.intern()实际上都返回s1;而 “java”是保留关键字在s3之前出现过了,所以s3.intern()返回的不是s3。

 

38、32位虚拟机 VS 64位虚拟机

由于指针膨胀和各种数据类型对齐补白的原因,运行于64位系统上的Java应用需要消耗更多的内存(通常比32位的增加10%~30%的内存开销) ;此外,64位虚拟机的运行速度比32位的大约有15%左右的性能差距。

不过,64位虚拟机也有它的优势:首先能管理更多的内存,32位最多4GB,实际上还受OS允许进程最大内存的现在(Windows下2GB);其次,随着硬件技术的发展,计算机终究会完全过渡到64位,虚拟机也将过渡到64位。

 

39、Java Runtime.getRuntime.exec()

用来执行外部命令,JVM执行此命令的过程如下:

首先克隆一个和当前虚拟机有一样环境变量的进程,再用此新进程去执行外部命令,最后再退出该进程。

若频繁执行该命令,系统的CPU和内存消耗很大。

 

40、关于Java自动拆箱/装箱的注意点

1、对于Integer、Long,在自动装箱时会进行缓存:位于[-128,127]的每个数只有唯一一个对应的包装类实例,不在此范围的数则可以对应任意多个包装类实例。(Float、Double则不会缓存;Integer缓存范围的最大值可以配置,默认为127;自动装箱实际上是调用了类的valueOf()来返回实例,在该方法里进行判断是否直接取缓存对象,因此如果直接new则不会缓存)。

2、==运算符的作用:操作数是基本类型时对应值一样为true;操作数为引用类型时两操作数须是同种类型编译才能通,若它们指向同一对象则为true。包装类的==运算符在不遇到算术运算时不会自动拆箱

3、equals()方法的作用:对比的两者是同种类型且值一样时为true。包装类的equals()方法不处理数据转型关系

示例:

 1 Integer a=1;
 2 Integer b=1;
 3 Integer c=128;
 4 Integer d=128;
 5 Integer e=new Integer(1);
 6 Integer f=new Integer(1);
 7 Long g=1;
 8 Long h=1;
 9 Double i=1;
10 Double j=1;
11 
12 System.out.println(a==b);//true
13 System.out.println(c==d);//false,不在缓存范围
14 System.out.println(e==f);//false,不是自动装箱
15 System.out.println(g==h);//true,Long与Integer一样有缓存处理
16 System.out.println(i==j);//false,Double、Float没有缓存处理
 1         Integer a = 1;
 2         Integer b = 2;
 3         Integer c = 3;
 4         Long d = 3L;
 5 
 6         System.out.println(c == (a + b));// true,在缓存范围内
 7         System.out.println(c.equals(a + b));// true,是同种类型且值一样
 8 
 9         System.out.println(d == (a + b));// true,有运算符,自动装箱
10         // System.out.println(d == c);//类型不同,编译不通过
11         System.out.println(d.equals(a + b));// false,不是同种类型
12         System.out.println(c.equals(d));// false,不是同种类型

 

41、

Java中整数对象的Cache:

  • 只有整数才会被Cache(Character、Byte、Short、Integer、Long),且只有在自动装箱时才会Cache
  • Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行,Integer的默认范围为-128到127。

Java整数的比较(涉及到自动拆箱/装箱):

1、若两数有一个是基本类型,则都按基本类型比较。如:

1 int i = 1300;
2 Integer i1 = 1300;
3 Integer i2 = new Integer(1300);
4 System.out.println(i == i1);//true
5 System.out.println(i == i2);//true
6 //只要两数含基本类型,就按基本类型比,不存在Cache问题。

2、否则,都是包装类对象,此时需要考虑自动装箱时的Cache问题

1 Integer i1 = 0;
2 Integer i2 = new Integer(0);
3 Integer i3 = 0;
4 System.out.println(i1 == i2);//false,始终为false
5 System.out.println(i1==i3);//true,若值都从0变为大于Cache范围的数如129,这次为false

 

 42、同步容器与并发容器

容器(Collection)有List、Set、Map的实现类等,它们不是线程安全的。为了线程安全,有:

同步容器:简单粗暴地加排它锁,如Vector、HashTable、Stack等

并发容器:同步容器直接加排它锁导致效率低, JDK5中添加了新的concurrent包,其中包含了很多并发容器,这些容器针对多线程环境进行了优化,大大提高了容器类在并发环境下的执行效率。如CopyOnWriteArrayList、ConcurrentLikedQueue、ArrayListBlockingQueue、LinkedBlockingQueue、ConcurrentHashMap等。

 

43、类型提升

1         byte x = 64;
2         int i = x << 2;
3         byte j = (byte) (x << 2);
4         System.out.println(i + " " + j);// 256 0

 45、关于除以0:

1 System.out.println(100 / 0.0);// Infinity
2 System.out.println(100.0 / 0);// Infinity
3 System.out.println(100.0 / 0.0);// Infinity
4 System.out.println(100 / 0);//java.lang.ArithmeticException: / by zero
5 System.out.println(3 / 2 + 1.0);// 2.0

原因:若操作符的左右操作数中最高类型是浮点数,则由于类型提升两操作数都按浮点数运算,再由于计算机无法精确表示浮点数所以此时除数的0在计算机内部实际上不是0,故前三者不会出现除以0的异常。

46、Java中获取类对象的方法

1、通过全限定类名字符串获取: Class.forName("ucar.buaa.act.Imtg.Grid"); 

2、通过类.class获取: ucar.buaa.act.Imtg.Grid.class 

3、通过实例对象.getClass获取: Main myMain = new Main();   myMain.getClass(); ,若有继承关系,还可以 myMain.getClass().getSuperclass(); 

47、Java反射:(java的访问控制是停留在编译层的,它不会在.class文件中留下任何的痕迹,只在编译的时候进行访问控制的检查。因此,通过反射的手段,是可以访问任何包下任何类中的成员,例如,访问类的私有成员也是可能的)

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

Java反射机制主要提供了以下功能: 在运行时判断任意一个类所具有的成员变量和方法;在运行时构造任意一个类的对象;在运行时判断任意一个对象所属的类;在运行时调用任意一个对象的方法;生成动态代理。

更多详见:https://www.cnblogs.com/z-sm/p/6746210.html

 

48、接口和抽象类的区别:(抽象类是is a的关系,接口是like a的关系)

1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类
6、抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。
7、抽象类里可以没有抽象方法
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。

特别是对于公用的实现代码,抽象类有它的优点。抽象类能够保证实现的层次关系,避免代码重复。然而,即使在使用抽 象类的场合,也不要忽视通过接口定义行为模型的原则。从实践的角度来看,如果依赖于抽象类来定义行为,往往导致过于复杂的继承关系,而通过接口定义行为能 够更有效地分离行为与实现,为代码的维护和修改带来方便。

 49、Java静态类只能作为内部类,即静态类一定是静态内部类。静态内部类最大的好处在于可以隐藏自己(只让自己被所在外层的类用到),同时又可以访问到所在外层类的属性。和“非静态”的内部类相比,它可以放置一些静态的成员变量和方法定义,而非静态类不可以;而且,静态内部类不能访问外层类非静态的属性。

 50、Java异常:

Java异常是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出。

Java异常机制用到的几个关键字:try、catch、finally、throw、throws。

• try        -- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
• catch   -- 用于捕获异常。catch用来捕获try语句块中发生的异常。
• finally  -- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
• throw   -- 用于抛出异常。
• throws -- 用在方法签名中,用于声明该方法可能抛出的异常。

1、分类:

java.lang.Throwable是所有异常的根

java.lang.Error是错误,编译器不会检查Error。

当程序发生不可控的错误时,通常做法是通知用户并中止程序的执行。Error是throwable的子类,代表编译时间和系统错误,用于指示合理的应用程序不应该试图捕获的严重问题。Error由Java虚拟机生成并抛出,包括动态链接失败,虚拟机错误等。程序对其不做处理。

java.lang.Exception是异常,分为:Checked异常和RuntimeException。Exception类本身、以及Exception的子类中除了"RuntimeException及其子类"之外的其它子类都属于Checked异常。Java异常分为checked、unchecked异常两类,对于前者,必须用代码显示捕获处理,后者则不需要。 

Checked异常代码里必须捕获或抛出到方法声明,编译器会检查此异常。Java认为Checked异常都是可被处理的异常,Java程序必须显示处理Checked异常。若程序没有处理,该程序在编译时就会发生错误无法编译。这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种:当前方法指导如何处理该异常则用try...catch处理;当前方法不知道如何处理则再定义该方法时抛出异常。常见的有Java.lang.ClassNotFoundException、Java.lang.NoSuchMetodException、java.io.IOException等

RuntimeException异常代码里不需要捕获或抛出到方法声明,编译器不会检查此异常。如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。常见的有Java.lang.ArithmeticException、Java.lang.ArrayStoreExcetpion、Java.lang.ClassCastException、Java.lang.IndexOutOfBoundsException、Java.lang.NullPointerException等。

示例: 

    public static void main(String[] args) {
//        throw new RuntimeException();//无错误,正常编译
        throw new FileNotFoundException();//编译不过,需要要么tray...catch处理、要么抛出到方法声明
    }

更多可参阅:Java异常简介及其架构《Effective Java》中关于异常处理的建议

 

 

51、公平锁、非公平锁

对于非公平锁:“同步队列”中的每个Node始终自旋地CAS竞争同步状态;对于公平锁:“同步队列”中的每个Node,当检测到它的pre不是队列头节点时就await()了,当队列头结点任务完成后,会执行signal()唤醒它的next节点。

 

52、Java HashMap的key可以为byte[]类型吗?(假设我们希望内容一样的数组当成一样的key)

不行,因为HashMap的key必须为Object类型或其子类。改为Byte[]在语法上没问题,但逻辑上会有问题,因为HashMap需要确保同一个对象的hashCode一样,而HashMap的hashCode()方法的默认实现为  (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) ,可见,当传进数组时候会以数组的地址为hashCode而不是根据数组内容作为key。

同理,以数组类型作为key均存在此问题。示例:

 1         HashMap<Byte[], Integer> map = new HashMap<>();
 2         Byte[] bts1 = new Byte[] { 1, 2, 3 };
 3         Byte[] bts2 = new Byte[] { 1, 2, 3 };
 4         System.out.println(Integer.toHexString(bts1.hashCode()) + "," + bts1.toString());// 659e0bfd,[Ljava.lang.Byte;@659e0bfd
 5         System.out.println(Integer.toHexString(bts2.hashCode()) + "," + bts2.toString());// 2a139a55,[Ljava.lang.Byte;@2a139a55
 6 
 7         map.put(bts1, 1);
 8         map.put(bts2, 2);
 9         System.out.println(map.get(bts1));// 1
10         System.out.println(map.get(bts2));// 2

 

53、在不同语言中,字符串的表示及字符串引号中包含转义字符时的打印结果:(设字符串为"hel\nlo")

Java:(只能双引号表示,会换行)  "hel\nlo"会换行。

JavaScript:(单、双引号表示,功能一样,均会换行)“hel\nlo”、'hel\nlo'均会换行。引号里可以嵌套一层引号,只要内层引号与外层不同是大引号或同是小引号。

Ruby:(双引号换行,单引号不换)  “hel\nlo”会换行、'hel\nlo'则不会换行而是原原本本打印字面内容。

Go:(双引号换行、反引号不换)  “hel\nlo”会换行、`hel\nlo`不会换行而是原原本本打印字面内容。

 

54、请求重定向和请求转发的区别:

  • 一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理,称之为请求转发/307。
  • 一个web资源收到客户端请求后,通知浏览器去访问另外一个web资源进行处理,称之为请求重定向/302

55、Java8链式语法风格:

基本操作:

        List<Integer> nums = Arrays.asList(1, 1, null, 2, 3, 4, null, 5, 6, 7, 8, 9, 10);
        System.out.println("求和:" + nums.stream()// 转成Stream
                .filter(team -> team != null)// 过滤
                .distinct()// 去重
                .mapToInt(num -> num * 2)// map操作
                .skip(2)// 跳过前2个元素
                .limit(4)// 限制取前4个元素
                .peek(System.out::println)// 流式处理对象函数
                .sum());//

将两个列表合并成一个:

        List<Integer> lista = Arrays.asList(1, 2, 3);
        List<Integer> listb = Arrays.asList(3, 4, 5, 6);
        List<List> lists = Arrays.asList(lista, listb);
        System.out.println(lists.stream().flatMap(e -> e.stream())// 将两个list合并成一个
                .collect(Collectors.toList()));

 

 

 56、枚举(参考资料:深入理解java enum

1、原理:对编译后的class文件javap反编译可以看出,定义的枚举类继承自java.lang.Enum抽象类且通过public static final定义了几个常量作为枚举常量。示例:

 1 //定义枚举类型
 2 enum Day {
 3     MONDAY, TUESDAY, WEDNESDAY,
 4     THURSDAY, FRIDAY, SATURDAY, SUNDAY
 5 }
 6 
 7 //对应的完整内容
 8 //反编译Day.class
 9 final class Day extends Enum
10 {
11     //编译器为我们添加的静态的values()方法
12     public static Day[] values()
13     {
14         return (Day[])$VALUES.clone();
15     }
16     //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
17     public static Day valueOf(String s)
18     {
19         return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
20     }
21     //私有构造函数
22     private Day(String s, int i)
23     {
24         super(s, i);
25     }
26      //前面定义的7种枚举实例
27     public static final Day MONDAY;
28     public static final Day TUESDAY;
29     public static final Day WEDNESDAY;
30     public static final Day THURSDAY;
31     public static final Day FRIDAY;
32     public static final Day SATURDAY;
33     public static final Day SUNDAY;
34     private static final Day $VALUES[];
35 
36     static 
37     {    
38         //实例化枚举实例
39         MONDAY = new Day("MONDAY", 0);
40         TUESDAY = new Day("TUESDAY", 1);
41         WEDNESDAY = new Day("WEDNESDAY", 2);
42         THURSDAY = new Day("THURSDAY", 3);
43         FRIDAY = new Day("FRIDAY", 4);
44         SATURDAY = new Day("SATURDAY", 5);
45         SUNDAY = new Day("SUNDAY", 6);
46         $VALUES = (new Day[] {
47             MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
48         });
49     }
50 }
枚举类反编译后的源码

java.lang.Enum抽象类定义了一些方法:

返回类型方法名称方法说明
int compareTo(E o) 比较此枚举与指定对象的顺序
boolean equals(Object other) 当指定对象等于此枚举常量时,返回 true。
Class<?> getDeclaringClass() 返回与此枚举常量的枚举类型相对应的 Class 对象
String name() 返回此枚举常量的名称,在其枚举声明中对其进行声明
int ordinal() 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)
String toString() 返回枚举常量的名称,它包含在声明中
static<T extends Enum<T>> T static valueOf(Class<T> enumType, String name) 返回带指定名称的指定枚举类型的枚举常量。

 主要源码:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    private final String name; //枚举字符串名称

    public final String name() {
        return name;
    }

    private final int ordinal;//枚举顺序值

    public final int ordinal() {
        return ordinal;
    }

    //枚举的构造方法,只能由编译器调用
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

    public String toString() {
        return name;
    }

    public final boolean equals(Object other) {
        return this==other;
    }

    //比较的是ordinal值
    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;//根据ordinal值比较大小
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        //获取class对象引用,getClass()是Object的方法
        Class<?> clazz = getClass();
        //获取父类Class对象引用
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }


    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        //enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值   
        //enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值       
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    //.....省略其他没用的方法
}
java.lang.Enum

2、可以把枚举类型当成常规类,即我们可以向枚举类中添加方法和变量。但是枚举常量定义必须在方法定义前面,否则编译报错。示例:

 1 public enum Day2 {
 2     MONDAY("星期一"),
 3     TUESDAY("星期二"),
 4     WEDNESDAY("星期三"),
 5     THURSDAY("星期四"),
 6     FRIDAY("星期五"),
 7     SATURDAY("星期六"),
 8     SUNDAY("星期日");//记住要用分号结束
 9 
10     private String desc;//中文描述
11 
12     /**
13      * 私有构造,防止被外部调用
14      * @param desc
15      */
16     private Day2(String desc){
17         this.desc=desc;
18     }
19 
20     /**
21      * 定义方法,返回描述,跟常规类的定义没区别
22      * @return
23      */
24     public String getDesc(){
25         return desc;
26     }
27 
28     public static void main(String[] args){
29         for (Day2 day:Day2.values()) {
30             System.out.println("name:"+day.name()+
31                     ",desc:"+day.getDesc());
32         }
33     }
34 
35     /**
36      输出结果:
37      name:MONDAY,desc:星期一
38      name:TUESDAY,desc:星期二
39      name:WEDNESDAY,desc:星期三
40      name:THURSDAY,desc:星期四
41      name:FRIDAY,desc:星期五
42      name:SATURDAY,desc:星期六
43      name:SUNDAY,desc:星期日
44      */
45 }
枚举类型自定义方法
public enum EnumDemo3 {

    FIRST{
        @Override
        public String getInfo() {
            return "FIRST TIME";
        }
    },
    SECOND{
        @Override
        public String getInfo() {
            return "SECOND TIME";
        }
    }

    ;

    /**
     * 定义抽象方法
     * @return
     */
    public abstract String getInfo();

    //测试
    public static void main(String[] args){
        System.out.println("F:"+EnumDemo3.FIRST.getInfo());
        System.out.println("S:"+EnumDemo3.SECOND.getInfo());
        /**
         输出结果:
         F:FIRST TIME
         S:SECOND TIME
         */
    }
}
枚举类型中定义抽象方法

3、定义的枚举类型无法被继承(看反编译后的源码可知类被final修饰了)也无法继承其他类(因其已默认继承了Enum类,而Java只允许单继承),但可以实现接口。一个很好的示例:

public enum Meal{
  APPETIZER(Food.Appetizer.class),
  MAINCOURSE(Food.MainCourse.class),
  DESSERT(Food.Dessert.class),
  COFFEE(Food.Coffee.class);
  private Food[] values;
  private Meal(Class<? extends Food> kind) {
    //通过class对象获取枚举实例
    values = kind.getEnumConstants();
  }
  public interface Food {
    enum Appetizer implements Food {
      SALAD, SOUP, SPRING_ROLLS;
    }
    enum MainCourse implements Food {
      LASAGNE, BURRITO, PAD_THAI,
      LENTILS, HUMMOUS, VINDALOO;
    }
    enum Dessert implements Food {
      TIRAMISU, GELATO, BLACK_FOREST_CAKE,
      FRUIT, CREME_CARAMEL;
    }
    enum Coffee implements Food {
      BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
      LATTE, CAPPUCCINO, TEA, HERB_TEA;
    }
  }
} 
枚举类实现接口

 4、枚举与单例:使用枚举单例的写法,我们完全不用考虑序列化和反射的问题。枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性(也说明了只有Java中只有编译器能创建枚举实例)。

如何确保反序列化时不会破坏单例:根据valueOf(name)得到反序列化后对象,valueOf根据枚举常量名获取对应枚举常量

public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                              String name) {
      T result = enumType.enumConstantDirectory().get(name);
      if (result != null)
          return result;
      if (name == null)
          throw new NullPointerException("Name is null");
      throw new IllegalArgumentException(
          "No enum constant " + enumType.getCanonicalName() + "." + name);
  }




Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            //getEnumConstantsShared最终通过反射调用枚举类的values方法
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            //map存放了当前enum类的所有枚举实例变量,以name为key值
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }
    private volatile transient Map<String, T> enumConstantDirectory = null;
valueOf

如何确保反射不会破坏单例:反射源码里对于枚举类型反射直接抛异常所以反射生成不了枚举类型实例

public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
  //获取枚举类的构造函数(前面的源码已分析过)
   Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
   constructor.setAccessible(true);
   //创建枚举
   SingletonEnum singleton=constructor.newInstance("otherInstance",9);
  }

//运行结果
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at zejian.SingletonEnum.main(SingletonEnum.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)



//newInstance源码
 public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        //这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
View Code

在单例中,枚举也不是万能的。在android开发中,内存优化是个大块头,而使用枚举时占用的内存常常是静态变量的两倍还多,因此android官方在内存优化方面给出的建议是尽量避免在android中使用enum。

5、EnumMap与EnumSet:见上述参考资料。

前者与HashMap类似,只不过key是Enum类型且不能为null。

后者则采用位向量实现,对于枚举值个数少于64的用一个long来标记(RegularEnumSet)否则用long[ ]来标记(JumboEnumSet)。

 

 57、获取本机的所有IP地址:

 1     public static void getIPs() {
 2 
 3         Enumeration<NetworkInterface> netInterfaces = null;
 4         try {
 5             netInterfaces = NetworkInterface.getNetworkInterfaces();
 6             while (netInterfaces.hasMoreElements()) {
 7                 NetworkInterface ni = netInterfaces.nextElement();
 8                 System.out.println("DisplayName:" + ni.getDisplayName());
 9                 System.out.println("Name:" + ni.getName());
10                 Enumeration<InetAddress> ips = ni.getInetAddresses();
11                 while (ips.hasMoreElements()) {
12                     System.out.println("IP:" + ips.nextElement().getHostAddress());
13                 }
14                 System.out.println();
15             }
16         } catch (Exception e) {
17             e.printStackTrace();
18         }
19     }

 获取作为服务端的本机的IP,以便其他机子能连:

 1     /** 获取本服务端的非环回IPv4 IP,在机子有很多IP时有可能出错 */
 2     public static String getIPv4() {
 3         String localip = null;// 本地IP,如果没有配置外网IP则返回它
 4         String netip = null;// 外网IP
 5         try {
 6             Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
 7             InetAddress ip = null;
 8             boolean finded = false;// 是否找到外网IP
 9             while (netInterfaces.hasMoreElements() && !finded) {
10                 NetworkInterface ni = netInterfaces.nextElement();
11                 Enumeration<InetAddress> addressSet = ni.getInetAddresses();
12                 // System.out.println(String.format("isLoopback:%s, isUp:%s, isVirtual:%s, isPointToPoint:%s",
13                 // ni.isLoopback(), ni.isUp(), ni.isVirtual(), ni.isPointToPoint()));
14                 if (ni.isUp() && !ni.isLoopback()) {
15                     while (addressSet.hasMoreElements()) {
16                         ip = addressSet.nextElement();
17                         // System.out.println(ni.getName() + "; " + ip.getHostAddress() + "; ip.isSiteLocalAddress()="
18                         // + ip.isSiteLocalAddress() + "; ip.isLoopbackAddress()=" + ip.isLoopbackAddress());
19                         if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
20                                 && ip.getHostAddress().indexOf(":") == -1) {// 外网IP
21                             netip = ip.getHostAddress();
22                             finded = true;
23                             break;
24                         } else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
25                                 && ip.getHostAddress().indexOf(":") == -1) {// 内网IP
26                             localip = ip.getHostAddress();
27                         }
28                     }
29                 }
30             }
31         } catch (SocketException e) {
32             e.printStackTrace();
33         }
34         if (netip != null && !"".equals(netip)) {
35             return netip;
36         } else {
37             return localip;
38         }
39     }
View Code

 58、通过反射获取对象各字段值:

    public static Map<String, Object> copyFildsToMap(Object obj, Field[] fields) {
        Map<String, Object> desMap = new HashMap<>();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                desMap.put(field.getName(), field.get(obj));
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return desMap;
    }

//使用 
Student s=new Student();
Map<String,Object> map=copyFieldsToMap(s,s.getClass.getDeclaredFields);

 

59、Map和Bean间相互转换

    /**
     * Map --> Bean:利用Introspector、PropertyDescriptor实现。Bean中有public set方法的属性才会被赋值。
     * 
     * @param <T>
     * 
     * @param map
     *            待转换者
     * @param obj
     *            Bean实例
     */
    public static void transMap2Bean(Map<String, Object> map, Object obj) {
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();// 有public get或public set方法的属性才会被获取到,此外还一定额外会有个只带有public
                                                                                            // get方法的class属性
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                if (!key.equals("class") && map.containsKey(key)) {// 会自动有个只有public get方法的class属性
                    Method setter = property.getWriteMethod();// 获取属性的public set方法,故属性有public set方法才能获取到,否则为null
                    if (null == setter) {
                        continue;
                    }
                    Object value = map.get(key);
                    setter.setAccessible(true);
                    setter.invoke(obj, value);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Bean --> Map:利用Introspector、PropertyDescriptor实现。Bean中有public get方法的属性才会被包装到Map。
     * 
     * @param obj
     *            指定的对象实例
     * @return
     */
    public static Map<String, Object> transBean2Map(Object obj) {
        if (obj == null) {
            return null;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();// 有public get或public set方法的属性才会被获取到,此外还一定额外会有个只带有public
                                                                                            // get方法的class属性

            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                if (!key.equals("class")) {// 会自动有个只有public get方法的class属性
                    Method getter = property.getReadMethod();// 获取属性的public get方法,故属性有public get方法才能获取到,否则为null
                    if (null == getter) {
                        continue;
                    }
                    getter.setAccessible(true);
                    Object value = getter.invoke(obj);
                    map.put(key, value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * Bean --> Map:利用反射将指定对象的指定属性及值包装到Map。不要求属性一定得有get方法。
     * 
     * @param obj
     *            指定的对象实例
     * @param fields
     *            该对象的属性集合
     * @return
     */
    public static Map<String, Object> copyFieldsToMap(Object obj, Field[] fields) {
        if (null == obj) {
            return null;
        }
        Map<String, Object> desMap = new HashMap<>();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                desMap.put(field.getName(), field.get(obj));
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return desMap;
    }
View Code

60、zip文件contentType

windows: application/x-zip-compressed
Linux,Mac: application/zip

61、IdentityHashMap与HashMap区别:前者用==比较key是否一样(即是否是同一个对象)、而后者在两个key均为null或equals成立时认为两个key一样。

 

62、Java数据类型:8中数据类型 、引用类型、数组

String[]names= {"liming"};

System.out.println(names.getClass().getTypeName());//java.lang.String[]

System.out.println(names.getClass().getDeclaredFields().length);//0

System.out.println(names instanceof Object);//true

System.out.println(names instanceof String[]);//true

System.out.println(names instanceof Object[]);//true

数组也是Object的实例,但其是一种特殊的引用类型:之所以说特殊是因为其没有Field,如对于String[] names={"liming"},names.getClass.getDeclaredFields的结果数组中大小为0。

 

63、JDK的SPI(Service Provider Interface)机制

SPI是JDK自身提供的一种能力,用于发现(实现了某接口的)服务提供者。

我们已经知道,Java多态使得我们可以面向接口编程,这样我们可以以改变很少的业务代码的代价来切换接口的具体实现。SPI更进一步,我们的业务代码可以完全面向接口编程,在程序执行过程动态发现接口的实现类。通常是调用者定义接口(如java.sql.Driver),而服务提供商实现该接口(如com.mysql.jdbc.Driver)

在JDBC4.0之前,通常会用Class.forName("com.mysql.jdbc.Driver")先加载数据库相关的驱动,然后再进行获取连接等的操作;而JDBC4.0之后不需要Class.forName来加载驱动,直接获取连接即可,其就是使用了Java的SPI扩展机制来实现

详情参阅:https://juejin.im/post/5af952fdf265da0b9e652de3

示例:

 

SPI文件:在META-INF/services/下创建文件,文件名与接口全限定名一样,如: com.mysqi.IMyServiceProvider ;文件中写该接口的具体实现类的全限定名,若有多个则每行一个。

代码:

//IMyServiceProvider interface
package com.myspi;
public interface IMyServiceProvider {
    public String getName();
}


//Implementation of IMyServiceProvider interface
package com.marchon.learn.spidemo;
import com.myspi.IMyServiceProvider;
public class MyServiceProviderImpl1 implements IMyServiceProvider {
    public String getName() {
        return "provider1";
    }
}


//main
package com.marchon.learn.spidemo;

import java.util.Iterator;
import java.util.ServiceLoader;

import com.myspi.IMyServiceProvider;

public class Main {
    public static void main(String[] args) {
        ServiceLoader<IMyServiceProvider> serviceLoader = ServiceLoader.load(IMyServiceProvider.class);
        Iterator<IMyServiceProvider> iterator = serviceLoader.iterator();
        
        
        while (iterator.hasNext()) {
            IMyServiceProvider service = iterator.next();
            System.out.println(service.getName() + " " + service.hashCode());
        }
    }
}
View Code

进阶:Spring自动配置的原理

SpringBoot中很多库只要引入Maven依赖库的默认配置就会自动生效(自动成为Bean被注入到IOC容器),即使main方法的componentscan basepath中未包含该库的配置文件路径。其原理与SPI很像。原理:

在库的META-INF/spring.factories里指定此库要被自动生效的配置类的全限定名,Spring的SpringFactoriesLoader会扫描classpath上各库里的spring.factories文件、自动创建文件里指定的配置类对应的实例、将实例注册到IOC容器,从而库的配置会自动生效。

“@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。”

实例:

 

 

 

 

 

64、关于类型转换

在语法上:

List<Object>对象可以赋给Collection<Object>变量,而Collection<String>不能赋给Collection<Object>变量。即 同类型的子泛型 不能转为同类型的父泛型

Integer[]对象可赋给Object[]变量,而int[]对象不可赋给Object[]对象。即引用类型的数组对象可以赋给Object[]变量,而基本类型的数组对象不能赋给Object[]变量

对于语法上允许的转换,虽然编译器不会报错,但使用者需要确保赋值时类型准确否则运行时会报错。如Number[] numbers=new Integer[3]; numbers[0]=1.2f; 编译正确但运行时会报错。

 

65、未知类型的数组遍历

对于一个数组ary,其中元素的类型未知,可能是基本类型也可能是对象类型,如何遍历?

容易想到的一个方法是将之视为Object[] objs=(Object[])ary; 然后对objs遍历即可。但此法存在问题:若ary中元素是基本类型,则该赋值会报类型转换错误。

可行的方法:通过Array.getLength(ary)获取数组ary的长度,然后通过Array.get(ary, i)遍历每个元素。

 

20191125

 66、接口的default method

java 8为接口引入了default,一大用途是通过default method为Collection类增加Stream API及Lambda表达式支持。

interface default method模糊了interface和abstrat class之间的区别

interface default method的一个问题:一个类A同时实现两个接口,若两接口都有同样签名的default method,则编译器不能确定A是要继承哪个接口的方法。解决:编译器会报错,要求A一定得重写同名方法

interface default method不能override a method from java.lang.Object,因为Object是一切类的基类,若允许override则会造成混淆。发生这种情况时编译器会报错。

可参阅:https://www.journaldev.com/2752/java-8-interface-changes-static-method-default-method

 

67、接口的static method

java 8接口也支持static method。与普通的static method类似,其不可被继承。区别在于:接口的实现类的实例无法访问到该接口的静态方法,只能通过接口来访问。而普通的中的静态方法对于子类对象来说是可见的。

此外,与default method类似,接口的default method也不能覆盖Object中的方法。

 

接口的default方法和static方法的区别?类似于类的实例方法与静态方法的区别:

异:default方法可被继承和覆盖(多default method继承问题:强制重写),而static方法不可(与类的静态方法不同,接口的静态方法对于子类来说是不可见的,只能通过接口去访问,故遑论覆盖了)

同:都不能与Object中的方法的声明一样。

 

68、为什么说反射慢?

无法被编译器进行代码优化

被创建的类实例或调用的方法需要去查找

参数需都被当成Object或Object[]

需要进行各种检查等

  1. The compiler can do no optimization whatsoever as it can have no real idea about what you are doing. This probably goes for the JIT as well
  2. Everything being invoked/created has to be discovered (i.e. classes looked up by name, methods looked at for matches etc)
  3. Arguments need to be dressed up via boxing/unboxing, packing into arrays, Exceptions wrapped in InvocationTargetExceptions and re-thrown etc.
  4. Check that there's a parameterless constructor
  5. Check the accessibility of the parameterless constructor
  6. Check that the caller has access to use reflection at all
  7. Work out (at execution time) how much space needs to be allocated
  8. Call into the constructor code (because it won't know beforehand that the constructor is empty)
  9. etc.

可参阅:https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow

 

69、如何以简单的方式让程序出现OutOfMemory/StackOverFlow?

产生OutOfMemory:创建大量线程

StackOverFlow:方法递归调用自己。如重写toString()方法,在方法内部调用this.toString()

 

70、关于代码兼容旧版本

通常理解的兼容是语法上的兼容,若要做到向旧代码兼容,则新代码里只能增加新的API而不能改或删掉旧API的语法。然而这其实只是表面,由于代码最终都是转化为二进制代码的,所以其本质是需做到binary compatible (BC) 。

对于动态类型语言,由于是解释执行的,故做到语法兼容确实就能做到binary compatible。

对于强类型语言,则做到语法兼容并不能保证binary compatible。相关可参阅:https://airbrake.io/blog/java-exception-handling/incompatibleclasschangeerrorhttps://stackoverflow.com/questions/1980452/what-causes-java-lang-incompatibleclasschangeerror

以Java为例:假设一个library里某个类的某方法本来是非static的,且有一个client引用了该方法,现将该方法改为static的且更新client的引用库但client未重新编译,则会出现binary incompatible。

真实场景:项目Expserver直接依赖Auth项目、I18项目,Auth项目也直接依赖I18n项目;将I18n项目中的某个非静态方法改为静态方法;重新编译I18n项目和Expserver项目,则Auth项目中用到I18n项目该方法时会报 java.lang.IncompatibleClassChangeError: Expecting non-static method  

 

71、程序运行时获取主类的全限定名

法1:

//代码
System.err.println(System.getProperty("sun.java.command"));
//结果
com.marchon.test.expserver2c.web.Test

法2:

//代码
    public static String getMainClassName() {
        StackTraceElement trace[] = Thread.currentThread().getStackTrace();// 背后的原理:return (new Exception()).getStackTrace();
        if (trace.length > 0) {
            return trace[trace.length - 1].getClassName();
        }
        return "Unknown";
    }


//结果
com.marchon.test.expserver2c.web.Test

72、List踩坑记:(更多可参阅:https://mp.weixin.qq.com/s/6gKyb_9cBGNs_GMQcw-VIQ

 

代码:

        String[] arrays = { "1", "2", "3" };
        ArrayList<String> list = new ArrayList<>(Arrays.asList(arrays));

        // 不可行方案:foreach迭代时通过list删除。foreach迭代语法糖底层会转为Iterator迭代
        for (String str : list) {
            if (str.equals("1")) {
                list.remove(str);//抛java.util.ConcurrentModificationException in java.util.ArrayList$Itr.checkForComodification
            }
        }

        // 可行方案:下标迭代时通过list删除
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals("1")) {
                list.remove(list.get(i)); // or list.remove(i);
            }
        }
        // 可行方案:Iterator迭代时通过iterator删除
        for (Iterator<String> it = list.iterator(); it.hasNext();) {
            String val = it.next();
            if (val.equals("1")) {
                it.remove();
            }
        }
迭代及删除方案

List迭代:Iterator迭代、foreach迭代(类似 for String str:strList 者)、下标迭代(类似 for i=0;i<list.size();i++ 者)。foreach迭代是个语法糖,会被编译为Iterator迭代

List迭代时删除:

可行方案:

1. 下标迭代时通过list删除,有删除指定元素和删除指定下标两种。注意这两法方记得将当前index减1,否则会漏掉一个元素

2. Iterator迭代时通过iterator删除:Iterator#remove

3. JDK1.8起 Collection#removeIf ,内部其实就是上种迭代方式。虽是个语法糖,但代码更简洁故推荐此法。

不可行方案:Iterator迭代时通过list删除、foreach迭代时通过list删除。对ArrayLisst、LinkedList适用,对CopyOnWriteArrayList不适用。

原因:与List的实现有关。有些List的实现在内部会记录插入、删除(更新则不会)等操作的次数(modCount变量),且这些List实现的Iterator#next方法内部会先checkForComodification(),即modCount != expectedModCount(iterator初始化时执行会expectedModCount = modCount ),check失败就会抛错 ConcurrentModificationException。显然,Iterator迭代时,若通过list删除则会修改modCount从而抛错、若通过iterator删除则维持两个count一样从而不报错。

另外,这里的“有些”是指ArrayLisst、LinkedList(还有哪些?),CopyOnWriteArrayList的实现并没有modCount、expectedModCount、Iterator check等约束故不会报错。

 综上,对于一些List,使用Iterator/foreach迭代时若通过list进行增加或修改,则迭代下一元素时就会报错,其原因是增删导致修改了List内部记录的modCount从而导致迭代的next方法里的checkForComodification失败,就跑错了。

Map会不会有迭代时并发增删导致的报错问题?自身不会,但对map的entrySet、values等方法返回的值做上述操作则会。实际上,Map接口无继承Iterable接口、Collection接口则继承了Iterable接口,所以对于Collection的一些实现类才可能有上述问题。

 

 ISO8601时间格式:"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"

 

 

73、Java Mail

javax.mail、com.sun.mail 提供了邮件发生与接收功能(可参阅:https://www.liaoxuefeng.com/wiki/1252599548343744/1319099923693601)。但他们太底层,通常可以借助SpringBoot的mail功能,更简洁。以下为后者的示例(可参阅:https://segmentfault.com/a/1190000019783608):

引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

配置:

spring:
  mail: # 邮件服务器地址等信息
    host: smtp.partner.outlook.cn
    username: edu.noreply@example.com
    password: xxxx
    port: 587
    protocol: smtp
    properties:
      mail:
        smtp:
          timeout: 25000
          auth: true
          starttls:
            enable: true
            required: true
          #ssl:
          #  trust: smtp.partner.outlook.cn
          #protocol: smtps
View Code

代码:

@Slf4j
@Component
public class SsMailSender {
    @Autowired
    JavaMailSender mailSender;// 注意需要在配置文件配置好邮件服务器的地址后 JavaMailSender Bean才会被自动创建,否则会报找不到Bean的错

    @Value("${spring.mail.username}")
    private String mailFrom;

    /**
     * 发送邮件的方法。
     * 
     * @param mailSubject     邮件标题
     * @param mailHtmlContent 邮件内容,支持html标签
     * @param mailsTo         接收方邮件地址,可有多个
     * @param mailsCc         邮件抄送地址,可有多个
     * @param mailsBcc        邮件隐秘抄送地址,可有多个
     * 
     * @return 发送成功与否
     */
    public boolean send(String mailSubject, String mailHtmlContent, Set<String> mailsTo, Set<String> mailsCc, Set<String> mailsBcc) {
        try {
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            mimeMessage.setContentID(System.currentTimeMillis() + "");
            MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
            helper.setFrom(mailFrom);
            if (null != mailsTo) {
                helper.setTo(mailsTo.toArray(new String[0]));// 接收邮件者
            }
            if (null != mailsCc) {
                helper.setCc(mailsCc.toArray(new String[0]));// 邮件抄送者
            }
            if (null != mailsBcc) {
                helper.setBcc(mailsBcc.toArray(new String[0]));// 邮件隐秘抄送者,其他收件人收到邮件内容时不会看到隐秘抄送者
            }
            helper.setSubject(mailSubject);
            helper.setText(mailHtmlContent, true);
//            helper.addAttachment("课程列表.xls", file);//附件
            mailSender.send(mimeMessage);

            return true;
        } catch (MessagingException e) {
            log.error("error when sending email: " + e.getMessage(), e);
            return false;
        }
    }
}
View Code

 

 

74、踩坑记

文件名中的空格在下载时变为加号、带加号的文件名出现在URL中时无法访问到文件

在文件下载中,后端通常会将文件名进行URL编码以免有些特殊字符前端无法显示,如:

 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8");//.replaceAll("\\+", "%20") 

另一方面,浏览器会对空格等字符进行URL编码后才传到后端,因此后端通常需要对浏览器传来的URL进行解码,如:

 objKey = URLDecoder.decode(objKey, utf8EncodeName); 

但是,上述做法会有一些corner case:

缺陷1:文件名带空格时,文件经浏览器弹窗下载下来后空格会变成加号。如:a  b.png -> a++b.png

缺陷2:URL中的文件名带加号时,后端decode后加号会变成空格。如:http://example.com/a%20+b.png -> http://example.com/a  b.png,此时若文件名确实是带有加号(a +b.png)的就会导致后端找不到对应文件。

原因,浏览器和很多后端库遵循的标准不一样(前后端对空格的编码结果不一样、对加号的编解码结果不一样),体现在:

后端(完全处理):java.net.URLEncoder编码时 将空格 -> 加号、将加号 -> %2B;java.net.URLDncoder解码时 将加号或%20  -> 空格、将%2B -> 加号。

浏览器(对加号不编解码):浏览器进行URL编码时 将空格 -> %20对加号不做编码;进行URL解码时 将%20 -> 空格、将%2B -> 加号、对加号不做解码

原因解释:

后端库会将空格编码成加号返回给浏览器,但浏览器对加号不做解码,从而导致上述缺陷1。

前端不会对URL中的加号进行编码,但后端库对加号会进行解码,从而导致缺陷2。(可通过 在浏览器输入带加号的URL并按回车看是否进行了转换 进行验证)

解决:从原因中可见前后端对%20、%2B这种转义字符进行编解码的处理结果是一致的,但对空格、加号的处理有不一致的地方,因此将对加号、空格的处理结果转成%20、%2B即可。因此,要么前端做处理来适配后端,要么后端做处理来适配浏览器,这里列出后者的方案:

针对缺陷1的解决:后端在完成encode后将内容中的加号替换成%20。

针对缺陷2的解决:后端在收到前端传来的URL时将其中的加号替换成%2B,然后进行decode。如:

 objKey = objKey.replaceAll("\\+", "%2B"); objKey = URLDecoder.decode(objKey, utf8EncodeName); 

参考资料:https://www.cnblogs.com/zhengxl5566/p/10783422.html

注:Java中的URLEncoder本意是用来把字符串编码成application/x-www-form-urlencoded MIME格式字符串,也就是说仅仅适用于URL中的query部分(W3C标准规定当Content-Type为application/x-www-form-urlencoded时,URL的query部分会被编码,其他部分则不会),但实际上URLEncoder会把给定的字符串都进行编码,从而会出现上述问题。因此若对于URL只希望对于query及后面的部分编码(这样浏览器才能识别),可以借助Spring的UriComponentsBuilder。上述两种情况综合而言,方法如下:

    public static String encodeUrl(String urlStr, boolean shouldEncodeReservedCharacter) {
        if (null == urlStr) {
            return null;
        } else {
            String res;
            if (shouldEncodeReservedCharacter) {
                try {
                    res = URLEncoder.encode(urlStr, StandardCharsets.UTF_8.name());
                } catch (UnsupportedEncodingException var4) {
                    throw new RuntimeException(var4.getMessage(), var4);
                }
            } else {
                res = UriComponentsBuilder.fromUriString(urlStr).encode(StandardCharsets.UTF_8).build().toString();
            }

            res = res.replaceAll("\\+", "%20");
            return res;
        }
    }




        System.err.println(normalizeFileName("/file/ abc+=d?", false));
        System.err.println(normalizeFileName("/file/ abc+=d?", true));
        System.err.println(normalizeFileName("/file/ abc+=d?/", false));
        System.err.println(normalizeFileName("/file/ abc+=d?/", true));
        String urlStr = "http://www.baidu.中国?name=张三 o&name=lisi&age&age=3#第一章";
        System.err.println(URLEncoder.encode(urlStr, StandardCharsets.UTF_8.name()));
        System.err.println(UriComponentsBuilder.fromHttpUrl(urlStr).encode(StandardCharsets.UTF_8).build().toString());
        System.err.println(UriComponentsBuilder.fromUriString(urlStr).encode(StandardCharsets.UTF_8).build().toString());
encodeUrl

 

 

75 A.class、a.getClass()、clz.getSuperclass()、clz.getDeclaredClass() 、clz.getDeclaredClasses() 的区别。(假设a是类A的实例)

A.class:类的属性。用于获取类A对应的Class对象,结果是 Class<A>的实例。结果一定非null。

a.getClass():在Object类中定义的方法。获取a实例所属的类A的Class对象(与A.class等价),结果是 Class<A>的实例。结果一定非null。

JVM特殊处理:当a已经是Class对象时,a.getClass()结果仍是a。

clz.getSuperClass():在Class类中定义的方法。获取Class对象表示的类(如A)的父类对应的Class对象。结果可能为null(不存在父类时)。

clz.getDeclaredClass():实例的方法,两处定义:

在Class类中定义。当clz表示的类A是在其他类内部定义的时,此时返回外层类的Class<?>的实例。结果可能为null(A不是在其他类内部定义时)。

在Enum中定义,效果与上类似,只不过特殊处理使得返回值最极端的情况是枚举类型自身,而不会是null。源码:

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }
View Code

clz.getDeclaredClasses() :在Class类中定义的方法。获取Class对象表示的类(如A)内部定义的其他类的Class对象列表。

综合示例:

public class Test {
    public static void main(String[] args) throws IOException {
        System.err.println(A.class);// class com.marchon.learn.expserver2b.web.A
        System.err.println(A.class.getSuperclass());// class java.lang.Object
        System.err.println(A.class.getDeclaringClass());// null
        System.err.println(Arrays.asList(A.class.getDeclaredClasses()));// []

        System.err.println(B.class);// class com.marchon.learn.expserver2b.web.B
        System.err.println(B.class.getSuperclass());// class com.marchon.learn.expserver2b.web.A
        System.err.println(B.class.getDeclaringClass());// null
        System.err.println(Arrays.asList(B.class.getDeclaredClasses()));// [class com.marchon.learn.expserver2b.web.B$C]

        System.err.println(C.class);// class com.marchon.learn.expserver2b.web.B$C
        System.err.println(C.class.getSuperclass());// class java.lang.Object
        System.err.println(C.class.getDeclaringClass());// class com.marchon.learn.expserver2b.web.B
        System.err.println(Arrays.asList(C.class.getDeclaredClasses()));// []

        System.err.println(Direction.class);// class com.marchon.learn.expserver2b.web.Direction
        System.err.println(Direction.class.getSuperclass());// class java.lang.Enum
        System.err.println(Direction.class.getDeclaringClass());// null
        System.err.println(Arrays.asList(Direction.class.getDeclaredClasses()));// []

        System.err.println(Fruit.class);// class com.marchon.learn.expserver2b.web.Fruit
        System.err.println(Fruit.class.getSuperclass());// class java.lang.Enum
        System.err.println(Fruit.class.getDeclaringClass());// null
        System.err.println(Arrays.asList(Fruit.class.getDeclaredClasses()));// []

        System.err.println(Direction.EAST.getClass());// class com.marchon.learn.expserver2b.web.Direction
        System.err.println(Direction.EAST.getClass().getSuperclass());// class java.lang.Enum
        System.err.println(Direction.EAST.getDeclaringClass());// 特殊 class com.marchon.learn.expserver2b.web.Direction
        System.err.println(Arrays.asList(Direction.EAST.getClass().getDeclaredClasses()));// []

        System.err.println(Fruit.APPLE.getClass());// class com.marchon.learn.expserver2b.web.Fruit$1
        System.err.println(Fruit.APPLE.getClass().getSuperclass());// class com.marchon.learn.expserver2b.web.Fruit
        System.err.println(Fruit.APPLE.getDeclaringClass());// 特殊 class com.marchon.learn.expserver2b.web.Fruit
        System.err.println(Arrays.asList(Fruit.APPLE.getClass().getDeclaredClasses()));// []

    }
}

class A {}

class B extends A {
    static class C {
    }
}

enum Direction {
    EAST(0), WEST(180), NORTH(90), SOUTH(270);
    private int angle;
    private Direction(int angle) {
        this.angle = angle;
    }
}

enum Fruit {
    APPLE {
        @Override
        public String getName() {
            return "APPLE";
        }
    };
    public abstract String getName();
}
View Code

 

20210905

76 Java中的并发同步策略

悲观锁:synchronized、ReentrantLock 等。问题:重锁,各线程对同一资源并发更新时各线程只能串行更新。

乐观锁:cas 操作,如AutomicInter 。问题:更新失败时循环重试直到成功,因此空耗CPU。

两者中和:LongAdder 等。其本质上仍是cas操作,只不过在多个线程更新同一变量出现冲突时复制一份变量来更新从而减少冲突和空耗CPU,获取变量值时是各变量副本值的和。详情可参阅:多线程更新变量的最快操作-公众号低并发编程

注:ReentrantLock、AutomicIntege、LongAdder 等都在 java.util.concurrent 包下,作者都是 Doug Lea;不同的是前两者是 java1.5引入的、而最后一个是java1.8引入的。

 

20220124

77 Java中Queue的主要方法的区别

add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞

 

20220428

Java中的clone方法

是 Object 类中的实例方法,是 native 方法,由JVM用其他语言实现。

因 Object 类是所有其他类的父类,故任何其他类都有该方法;其他类只有实现了 Cloneable 接口(该接口只是各标记接口,内部无任何方法),该类的实例的 clone 方法才能被调用,否则会报错。

一个实现了 Cloneable 接口的类的实例的 clone 方法后被调用时,会基于该实例浅拷贝一个实例,而无需开发者实现任何 clone 的逻辑。

当然,也可以重写clone方法来实现深拷贝或其他自定义逻辑。如何实现深拷贝?反序列化再序列化回来即可,最简单的方法。

综上所述,clone 方法的功能是用于类实例的浅拷贝,而无需开发者实现任何clone逻辑,其一个前提是类必须实现 Cloneable 接口。

ModelMapper、Spring框架中的 BeanUtils等工具可用于不同类间属性值的复制,其是通过反射实现的。当源、目标对象是同一种类型时,显然直接用clone更高效。

 

20220525

Java Zip 解压压缩包到指定目录:

    private static void unzipFile(String zipFilePath, String decompressDirPath) throws IOException {
        try (ZipFile zipFile = new ZipFile(zipFilePath, StandardCharsets.UTF_8)) {
            Enumeration<?> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                String entryName = entry.getName();
                Path decompressEntryPath = Paths.get(decompressDirPath, entryName);
                if (!entry.isDirectory()) {
                    File targetFile = decompressEntryPath.toFile();
                    if(!targetFile.exists()) {
                        targetFile.getParentFile().mkdirs();
                    }
                    try (InputStream srcZipInputStream = zipFile.getInputStream(entry);
                            FileOutputStream fileOutputStream = new FileOutputStream(targetFile)) {
                        IOUtils.copy(zipFile.getInputStream(entry), fileOutputStream);
                    }
                } else {
                    Files.createDirectories(decompressEntryPath);
                }
            }
        }
    }
View Code

 

20221201

负整数除法和求模的问题

在数学理论中余数是非负数,然而在不同的编程语言中对包含负数的除法实现可能不同,导致余数也不同,例如 -3%5 = -1余2 或 = 0余-3。

大多数语言的处理方式都与 C/Java 一致,采用了 truncate 除法,其特点是余数与被除数的正负符号相同(详见这篇文章),因此-3%5=-1、3%(-5)=3,这更符合数学里的运算因为跟都为整数的除法很像。

-12 % 10 结果是多少?有的语言是 -12 = 10 * (-1) + (-2)、有的语言是 -12 = 10 * (-2) + (+8) ,都有可能,Java里采用前者。怎么记呢?把被除数和除数理解成一维坐标轴上以0为起点的两个向量,然后按除数向量乘上系数后不能超过被除数向量来确定系数,就很好理解了。例如:

够被除的情况:

-12 ÷ 10 理解为 (0, -12) = (0, 10) *  (-1) + (0, -2),故 -12/10 商 -1、余 -2

12 ÷ -10 理解为 (0, 12) = (0, -10) *  (-1) + (0, 2),故商 -1 余 2

-12 ÷ -10 理解为 (0, -12) = (0, -10) *  (1) + (0, -2),故商 1 余 -2

12 ÷ 10 理解为 (0, 12) = (0, 10) *  (1) + (0, 2),故商 1 余 2

不够被除的情况:

-1 ÷ 10 理解为 (0, -1) = (0, 10) *  0 + (0, -1),故 -12/10 商为0、余数为-1

1 ÷ -10 理解为 (0, 1) = (0, -10) *  (0) + (0, 1),故商 0 余 1

-1 ÷ -10 理解为 (0, -1) = (0, -10) *  (0) + (0, -1),故商 0 余 -1

1 ÷ 10 理解为 (0, 1) = (0, 10) *  (0) + (0, 1),故商 0 余 1

结论:

余数的符号和被除数相同、商的符号由【让除数向量和被除数向量方向一致且长度不超过前者情况下后者尽可能长所需的系数】来确定(即被除数向量和除数向量同向则商为正、反向则为负,当然不够除情况下的商是例外固定为0)。

被除数、除数的符号组合有四种,不管哪种,其商和余数的绝对值都是一样的,都和被除数、除数都是整数情形下的结果一样!!故可按都是整数的来算,然后再考虑符号。

余数的绝对值比除数的绝对值小。

为了确保 a%k 为正,可做如下处理:mod=(a%k+k)%k 。这个特性在子数组和问题中有用到。

有什么用呢?例子:

LeetCode.7 定一个有符号数x求其反转结果,如 123 -> 321、-123 -> -321。能够想到的解法是  long res=0; while(x!=0) { res=10*res+x%10; x/=10; } return res; ,但笔者做时担心x为负数时求模结果和正数的不一样(即 -12%10 为8吗)故加了判断当x为负数则先转成正数计算然后最后再取反。显然,知道上述结论后,就没必要这样搞特殊了,完全和正数的处理一样。

 

20230406

Java 线程包括用户线程与守护线程两种,通过在线程 start 前设置 isDaemon 可将线程设置为守护线程。

区别:用户线程优先级高,JVM终止前会等待所有用户线程执行完;守护线程优先级低,JVM终止前不会等其执行完(当然也不绝对,例如调用线程的join方法)。故:用户线程会影响JVM退出而守护线程不会;不推荐在守护线程中的执行IO操作否则可能导致资源无法正确关闭;守护线程中的逻辑通常是无限循环,不会影响程序性能;守护线程用于后台支持的任务如垃圾回收、释放对象内存等。

Java 中线程的类型是继承的,即:用户线程创建的线程也是用户线程、守护线程创建的线程也是守护线程。

 

posted @ 2014-08-31 22:23  March On  阅读(1349)  评论(0编辑  收藏  举报
top last
Welcome user from
(since 2020.6.1)