Java开发基础


title: Java开发基础
date: 2021-05-04 04:28:02
tags: java基础

文章目录

Java基础


java特点
  • 面向对象 :方法,类,接口,文件等都可以看做对象
  • 健壮性 :有字节码校验,语法校验,支持异常等
  • 安全性:Java的存储分配是JVM管理的,以防恶意程序代码,再加上各种Java校验
  • 可移植性 :一次编译各处执行
  • 多线程:支持多线程
  • 动态性:多态,支持在类装载前修改运行的java字节码(字节码插桩),是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结 构上的变化。
  • 分布性:支持网络上的应用
基本数据类型
  • 1个字节等于8位二进制数
类型名称说明
整型int4个字节
short2个字节
long8个字节
byte1个字节
浮点型float4个字节
double8个字节
字符型char
布尔型boolean
基本数据类型转换
  • 隐式合法转换
  • 实心箭头之间的转换是不会丢失精度的,虚线箭头之间的转换是可能会丢失精度的
  • int转float精度会丢失,是因为int需要表示的数字只有整型,而float需要表示的数字还要表示小数,但int个float同样是4位,所以int比float表示的数字大,大的转小的所以可能会存在丢失,其他虚线转换同理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJZ2uOAV-1620147205101)(/images/Java开发基础/数据类型合法转换.png)]

  • 显示转换,除以上方向的转换属于显式转换,会存在精度丢失
关键字
  • 修饰符访问级别
名称说明本类同一个包类继承类其他类
private私有×××
default当无其他访问符时××
protected受保护的×
public公开的
名称说明
const常量(保留)
goto转到(保留)
private私有
default默认
protected受保护的
public公开的
class
interface接口,属于特殊的抽象类
在接口内的方法默认加上public abstract
在接口内的变量默认加上public static final
abstract抽象的,在子类或实现类必须实现
implements实现接口
extends继承类
new创建新对象
import引入包的关键字
package定义包的关键字
byte字节型
char字符型
boolean布尔型
short短整型
int整型
float浮点型
long长整型
double双精度
void无返回
null
true
false
if如果
else或者
else if或者,一般作为if和else,中间分支
while当满足条件时循环
for当满足条件时循环
do运行一段代码,一般和while一起使用
switch开关
支持byte,short,char,int
String(jdk7才支持)
Enum(枚举)
支持以上类型并支持包装类
case执行开关的结果
break跳出当前代码块的最上级块没跳出方法
continue跳出当前这一次循环,继续下一次循环
return返回并跳出当前方法
instanceof判断是否属于这个类的子类,使用方法:子类 instanceof 父类
catch捕捉异常并处理
finally在方法结束之后又回来执行的块
try尝试执行这块代码,一般和catch或finally配合使用
static静态的,存在jvm的方法区
final最终的不可被改变的,需要在编译前指定初始值
final修饰的类,不能被继承,这个类的方法默认会加上final
final修饰的方法,不能被重写
private修饰的方法会被隐式加上final
super调用父类,可调用父类的变量,方法,super()必须放在方法第一行
this当前类,this()必须放在方法的第一行
native本地,jvm封装的底层C方法
strictfp严格的,精准的(jdk1.2开始有),定义在类,方法,接口上会严格遵循浮点规范IEEE-754来执行,在strictfp定义范围内,浮点数不会因为不同环境而导致不一样,比较少用
synchronized锁,同步锁,重量级锁,基于监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的 。jdk1.6之后为优化重量锁的效率问题,引入了轻量锁和偏向锁。同一个 jvm下有效,不同jvm无效,分布式无效
transient短暂的,只能修饰变量,transient修饰的变量无法被序列化,无法通过反射获取
volatile易失的,保证不同线程的可见性,不保证原子性,对当前修饰变量禁止指令重排,既不能放在前面执行也不会放在后面执行,只会在当前顺序执行
throw方法内抛出异常
throws抛出异常到方法外
enum枚举
assert断言(jdk1.4新增),assert后必须跟一个为true的表达式,否则会抛出AssertionError
运算符
  • 除了自增加自减不进行转化外,其它情况当无long型时,所有非int类型转成int类型;有long类型时,都转成long类型
名字类型说明
+
-
单目运算符取正,取负:
当操作数是byte,short,char时会转换为int,返回结果是int
当操作数是int,long时,不转换
++
单目运算符不管是什么类型,不转换
+
-
*
/
%
双目运算符当两个操作数中没有long类型时,两个操作数中非int类型会先自动转换为int类型,再参与运算,返回结果为int;
当两个操作数中含有long类型时,两个操作数中非long类型会自动转换为long类型,再参与运算,返回结果为long;
&(与)
|(或)
^(异或)
位运算符当两个操作数中没有long类型时,两个操作数中非int类型会先自动转换为int类型,再参与运算,返回结果为int;
当两个操作数中含有long类型时,两个操作数中非long类型会自动转换为long类型,再参与运算,返回结果为long;
~(非)
位运算符当操作数是byte,short,char时会转换为int,返回结果是int
当操作数是int,long时,不转换
<< (带符号左移)
>>(带符号右移)
>>> (无符号右移)
位运算符当操作数是byte,short,char时会转换为int,返回结果是int
当操作数是int,long时,不转换
String类
1,先看一个String的例子
String s1 = "Runoob";              // String 直接创建
String s2 = "Runoob";              // String 直接创建
String s3 = s1;                    // 相同引用
String s4 = new String("Runoob");   // String 对象创建
String s5 = new String("Runoob");   // String 对象创建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jd9A8FHG-1620147205106)(/images/Java开发基础/String取值的例子.png)]

2,为什么不推荐在for循环内使用字符串拼接?
package com.javase.stringTest;

/**
 * @version 1.0
 * @Description
 * @Author Jimmy
 * @Date 2021/4/21 13:14
 */
public class StringTest {
    public static void main(String[] args) {
        String s = "";
        for (int i = 0; i < 1000; i++) {
            s = s + i;
        }
        System.out.println(s);
    }
}

//编译成字节码如下,一下字节码简单理解一下,之后会再讲解字节码中详细介绍其含义
Compiled from "StringTest.java"
public class com.javase.stringTest.StringTest {
  public com.javase.stringTest.StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String   定义一个String的变量s
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: sipush        1000				//for循环开始
       9: if_icmpge     37
      12: new           #3                  // class java/lang/StringBuilder  在for循环内new一个StringBuilder的类			
      15: dup
      16: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V 初始化StringBuilder类
      19: aload_1
      20: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;    StringBuilder调用append方法放入第一个字符串s
      23: iload_2
      24: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;  StringBuilder调用append方法放入第二个字符串i
      27: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;				StringBuilder调用toString返回String字符串s
      30: astore_1
      31: iinc          2, 1
      34: goto          5
      37: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      40: aload_1
      41: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: return
}
  • 结论:因为大量StringBuilder创建在堆内存中,肯定会造成效率的损失,所以在这种情况下建议在循环体外创建一个StringBuilder对象调用append()方法手动拼接。
3,为什么String是final修饰的却可以改变字符串内容?
  • 通过重新指向堆上面的变量而重新指向String常量池的字符串内容
4,JVM对String做的优化
package com.javase.stringTest;

/**
 * @version 1.0
 * @Description
 * @Author Jimmy
 * @Date 2021/4/21 13:14
 */
public class StringTest {
    public static void main(String[] args) {

        /**
         * 编译期确定
         * 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。
         * 所以此时的"a" + s1和"a" + "b"效果是一样的。故结果为true。
         */
        String s0 = "ab";
        final String s1 = "b";
        String s2 = "a" + s1;
        System.out.println((s0 == s2)); //result = true


        /**
         * 编译期间不确定
         * cc变量在赋值的时候:会new StringBuilder,然后调用append方法进行拼接,最后调用toString方法返回一个新的字符串
         */
        String a00 ="aabb";
        String aa = "aa";
        String bb = "bb";
        String cc = aa + bb.toString();
        System.out.println(a00 == cc);

        /**
         * 编译期无法确定
         * 这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定
         * 因此s0和s2指向的不是同一个对象,故上面程序的结果为false。
         */
        String s00 = "hellojava";
        final String s11 = getS1();
        String s22 = "hello" + s11;
        System.out.println((s00 == s22)); //result = false
    }

    public static String getS1() {
        return "java";
    }
}

//编译成字节码如下
Compiled from "StringTest.java"
public class com.javase.stringTest.StringTest {
  public com.javase.stringTest.StringTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String ab
       2: astore_1
       3: ldc           #3                  // String b
       5: astore_2
       6: ldc           #2                  // String ab
       8: astore_3
       9: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: aload_1
      13: aload_3
      14: if_acmpne     21
      17: iconst_1
      18: goto          22
      21: iconst_0
      22: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      25: ldc           #6                  // String aabb
      27: astore        4
      29: ldc           #7                  // String aa
      31: astore        5
      33: ldc           #8                  // String bb
      35: astore        6
      37: new           #9                  // class java/lang/StringBuilder
      40: dup
      41: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
      44: aload         5
      46: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      49: aload         6
      51: invokevirtual #12                 // Method java/lang/String.toString:()Ljava/lang/String;
      54: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      57: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      60: astore        7
      62: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      65: aload         4
      67: aload         7
      69: if_acmpne     76
      72: iconst_1
      73: goto          77
      76: iconst_0
      77: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      80: ldc           #14                 // String hellojava
      82: astore        8
      84: invokestatic  #15                 // Method getS1:()Ljava/lang/String;
      87: astore        9
      89: new           #9                  // class java/lang/StringBuilder
      92: dup
      93: invokespecial #10                 // Method java/lang/StringBuilder."<init>":()V
      96: ldc           #16                 // String hello
      98: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     101: aload         9
     103: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     106: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     109: astore        10
     111: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
     114: aload         8
     116: aload         10
     118: if_acmpne     125
     121: iconst_1
     122: goto          126
     125: iconst_0
     126: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
     129: return

  public static java.lang.String getS1();
    Code:
       0: ldc           #17                 // String java
       2: areturn
}

5,运行时常量池,字符串常量池 ,class常量池(以上是java内存分配的常量池,暂且不介绍class常量池)
  • 运行时常量池:Integer的常量池,Long的常量池等等

  • 字符串常量池:

    1. 为减少创建和操作String时的时间和空间而使用的一种技术
    2. 字符串常量池不会出现两个内容相同的String字符串,但会出现同一个内容的字符串被多个变量引用
    3. 字符串常量池是底层String Table实现的,JVM维护,是哈希表实现的,在String创建过多时哈希碰撞变多,效率会变慢
    4. 当new String(“aa”)时可能会创建2个对象,若字符串常量内存在aa字符串,则直接返回
    5. String 的intern方法,假如字符串常量池内存在,那么会直接返回这个字符串的引用而不会重新创建新值
    6. String +号拼接,StringBuilder,StringBuffer的效率比较,
    1)String是不可变字符序列(final修改),StringBuilder和StringBuffer是可变字符序列(有扩容)。
    2)执行速度StringBuilder > StringBuffer > String拼接。
    3)StringBuilder是非线程安全的,StringBuffer是线程安全的。
    
拆箱装箱
1,特点
  • 为更加贴近面向对象这一特点,将基本数据类型包装成对象
2,Java中基本数据类型的池化技术

//下面通过例子来看一下

   
   public class Main {
    public static void main(String[] args) {
   Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 321;
    Integer f = 321;
    Long g = 3L;
    Long h = 2L;
    Double x = 1.257;

    System.out.println(c == d);//true,cd同为Integer,3在Integer缓存范围内,调用拆箱,调用缓存池的值比较
    
    System.out.println(e == f);//false ,ef同为Integer,321不在缓存范围内,比较地址
    
    System.out.println(c == (a + b));//true,先算(a+b)拆箱计算得3,c是3,刚好又在缓存池,调用拆箱,调用缓存池值比较
    
    System.out.println(c.equals(a + b));//true,调用Integer的equals方法,先算a+b拆箱计算得3,c是3,刚好又在缓存池,调用拆箱,调用缓存池值比较
    
    System.out.println(g == (a + b));//true,先算a+b调用Long的拆箱操作得3,g是3,刚好又在Long的缓存池内,调用Long的缓存池值比较
    
    System.out.println(g.equals(a + b));//false,调用Long的equals方法,先计算a+b返回Integer类型,Integer不是Long的子类,直接返回false
    
    System.out.println(g.equals(a + h));//true,调用Long类型的equals方法,先计算a+h此处计算以为h是Long类型所以a调用Long类型的拆箱计算得出Long类型的3L,3刚好又在缓存池内,调用缓存池的值比较
    
    System.out.println(g == 3); //true ,同理
    System.out.println(x == 1.257); //true,同理
    System.out.println(e.equals(f));//true,同理
}

}

//总结:
//1, ==比较的是存储地址,equals比较的是值(重写了equals方法的另说)
//2,注意包装类和String类普遍有缓存池,缓存池是有范围的,若超过缓存范围(==不再是比较值,而是比较地址)若在缓存范围内(==比较的是从缓存池拿出的值比较),注意浮点数小数参与运算之后的比较(浮点数小数参与运算之后的结果因为二进制存储的问题,大概率会出现误差)
//3,装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过引用类型调用xxxValue实现的(详情请编译后看字节码)
//4,缓存池缓存的范围可以通过JVM的指令进行调整

泛型
  • 只在编译期有效,编译后泛型会被擦除
  • 定义上限extends
  • 定义下限super
  • 可定义在类,接口上,方法上
Java类加载顺序
如果类没有被加载

1、父类静态代码块和静态成员变量

2、子类静态代码块和静态成员变量

3、父类代码块和成员变量(用{} 括起的代码块)

4、父类构造方法

5、子类代码块和成员变量(用{} 括起的代码块)

6、子类构造方法

7、静态方法与非静态方法只有被调用的时候才会被加载

如果类已经被加载

静态代码块和静态变量就不用重复执行,再创建类对象时,只执行与实例相关的变量初始化和构造方法。

Java中的注解
  • 创建一个注解:@interface
  • 元注解:
    1. @Retention(保留期)
      • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
      • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到JVM中。
      • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载到JVM中。
    2. @Documented(添加文档)
    3. @Target(作用范围)
      • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
      • ElementType.CONSTRUCTOR 可以给构造方法进行注解
      • ElementType.FIELD 可以给属性进行注解
      • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
      • ElementType.METHOD 可以给方法进行注解
      • ElementType.PACKAGE 可以给一个包进行注解
      • ElementType.PARAMETER 可以给一个方法内的参数进行注解
      • ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
    4. @Interited(注解继承的意思,用在子注解上)
    5. @Repeatable (可重复的意思,被此注解声明的注解可重复使用在相同的对象上,1.8之后才有)
  • 常用的注解
    1. @Override:重写父类方法的标识注解
    2. @Deprecated:提醒调用者不建议使用
    3. @SuppessWarnings:忽略警告,如往指定了泛型集合内添加不支持的数据类型
    4. @SafeVarargs :忽略编译器unchecked警告
    5. @FunctionallInterface:在编译器对函数式的代码规范的检查,会抛出编译错误
异常
异常分类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XNnP37NV-1620147205108)(/images/Java开发基础/Java异常.png)]

throw和throws的区别是?
  • throw作用于方法内部抛出了一定要处理
  • throws声明在方法上,表示可能会抛出异常,可跟多个异常
Java中的流
Java IO流大致分类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bOR0PxAR-1620147205110)(/images/Java开发基础/JavaIO流分类.png)]

Java中的集合框架
集合框架的整体关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3T6SsOD1-1620147205112)(/images/Java开发基础/java集合框架关系图.png)]

常用集合介绍
  • ArrayList,数组实现
  • LinkedList,链表实现
  • Vector,向量,不常用
  • Stack,栈,特殊的情况使用
  • HashMap,数组+单向链表+红黑树实现,非常常用,按计算的HashCode排列
  • LinkHashMap,数据+双向链表+红黑树实现,非常常用,插入顺序排列
  • TreeMap,数组+红黑树实现,非常常用
  • HashTable,哈希表类似的结构,不常用
  • HashSet,数据结构用的是HashMap,木有自己的核心技术,非常常用
  • TreeSet,数据结构用的是TreeMap,木有自己的核心技术,非常常用
  • Iterator,遍历器
  • Queue,队列先进先出
  • Collections,集合的工具类
  • Arrays,数组的工具类
  • 以上常用集合框架都是线程不安全的,在多线程并发下会出现各种各样的线程安全问题,若想使用线程安全的集合可用Collections工具类进行线程安全增强,或者使用线程安全的集合
  • 综上所述,下面会重点介绍,List和Map
集合存储特点
名称具体实现是否有序存储元素是否可以重复
List
SetAbstractSet
HashSet
TreeSet
MapAbstractMapkey唯一,value可以重复
HashMapkey唯一,value可以重复
TreeMapkey唯一,value可以重复
ArrayList介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DE70Hf6T-1620147205113)(/images/Java开发基础/ArrayList的类图.png)]

  • 基本介绍:

    1. Object[] 数组实现,允许null值,可以存储
    2. 默认容量大小是10
    3. 支持扩容,扩容为原大小的1.5倍
    4. 什么时候扩容?当添加元素之后的size小于最小size的1.5倍时
    5. MAX_ARRAY_SIZE 为什么等于 Integer.MAX_VALUE - 8?这8的字节是用于保存数组的元数据(数组大小以及其他信息)。至于为什么是用Integer的大小,这个大小比较合适(如果尝试分配更大可能会导致OutOfMemoryError)
    6. ArrayList线程安全吗?不安全,它的操作没有和锁相关的。可以通过Collections.synchronizedCollection()增强实现线程安全的操作
    7. 实现RandomAccess接口有啥用?答:没啥用,就标记一下,被这个没啥用的接口标记的List使用什么算法比较快,如支持快速随机访问(for循环比迭代器快,可查看官网文
    8. 档)
    • add时的扩容源码:
    // ArrayList ->add() ->ensureCapacityInternal()->ensureExplicitCapacity->grow()
    
     /**
         * The maximum size of array to allocate.
         * Some VMs reserve some header words in an array.
         * Attempts to allocate larger arrays may result in
         * OutOfMemoryError: Requested array size exceeds VM limit
         */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            //新容量 = 旧容量*1.5
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //新容量小于最小容量的1.5倍
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            //新容量比数组最大容量还大
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            //进行数组拷贝,返回新数组
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
     private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    
    
LinkList介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yQKopq2W-1620147205114)(/images/Java%E5%9F%BA%E7%A1%80-JavaSE/LinkList%E7%9A%84%E7%B1%BB%E5%9B%BE.png)]

  • 基本介绍:
    1. 链表实现,Node[]数组实现,
    2. 每个数据节点,由Node组成,Node包含前驱节点,数据节点,后驱节点
    3. LinkList线程安全吗?是不安全的,多个线程操作同一个LinkList会抛出ConcurrentModificationException
  • add方法
// LinkList ->add() ->linkLast()
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        
        //若最后一个节点是null,那么新添加的节点作为第一个节点
        if (l == null)
            first = newNode;
        //否则,最后一个节点的下一个节点指向新节点    
        else
            l.next = newNode;
        size++;
        
        //用于 记录的修改次数,用作快速失败。如一边遍历,一边修改导致modCount意外改变,就会抛出ConcurrentModificationException
        modCount++;
    }
HashMap介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XToNoCbr-1620147205115)(/images/Java%E5%9F%BA%E7%A1%80-JavaSE/HashMap%E7%9A%84%E7%B1%BB%E5%9B%BE.png)]

  • 基本介绍:

    1. 数组+链表+红黑树实现,jdk1.8后引入了红黑树的结构和扩容优化

    2. key可以使用null,但不可重复。values可用null,可重复

    3. HashMap线程不安全,可用Collections的synchronizedMap方法使HashMap增加线程安全的功能。或者使用,线程安全的HashMap

    4. HashMap如何进行插入,简单说说?先计算key的hash值,然后放到哈希桶相应下标的链表后添加一个数据。这时候引入了两个问题:1,多个key有相同的hash值。 2,因为数据量大或者hash不够散列,导致链表长度过长,遍历效率低下。这些问题该如何解决呢?

    5. 多个key有相同的hash值该如何取舍?

      //开放定址法
      这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:
      
      Hi=H(key)+di)% m i=12,…,n
      
      其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有三种 线性探测再散列,二次探测再散列,伪随机探测再散列
      
      //再哈希法
      这种方法是同时构造多个不同的哈希函数
      
      Hi=RH1(key) i=12,…,k
      
      当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间
      
      //链地址法
      这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。
      
      链地址法适用于经常进行插入和删除的情况。
      
      //建立公共溢出区
      这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
      
  • HashMap默认参数和构造器的介绍
  • 默认参数
//默认容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 

//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//树化得阈值,大于等于8转化为红黑树
static final int TREEIFY_THRESHOLD = 8;

//取消红黑树的阈值,小于6时从红黑树转化为链表
static final int UNTREEIFY_THRESHOLD = 6;

//红黑树存储的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;

//Node数组,哈希桶
 transient Node<K,V>[] table;

//缓存的键值对
 transient Set<Map.Entry<K,V>> entrySet;

//表示这个HashMap的key个数
 transient int size;

//保存操作的HashMap的次数,用作快速失败,如多线程意外修改的时候导致modCount增加与同一个线程的modCount的值不一样就会抛出ConcurrentModificationException
transient int modCount;

//Hash 表的容量
int threshold;

//hash table的负载因子
 final float loadFactor;
  • 构造器,一共有4个构造器
    /**
     * @param  initialCapacity 初始容量
     * @param  loadFactor   hash table 的负载因子
     */
public HashMap(int initialCapacity, float loadFactor) 
    
      /** 
     * @param  initialCapacity 初始化容量 此时默认的负载因子是0.75f
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
   public HashMap(int initialCapacity) 
    
   //无参数构造器 此时的初始化容量是默认的16,负载因子是0.75f
   public HashMap() 
    
     /**
     *
     *此时的默认负载因子是默认的0.75f,初始容量是足以保存m这个map的容量
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
   public HashMap(Map<? extends K, ? extends V> m) 
    
    
    
    
   //构造器介绍: public HashMap(int initialCapacity, float loadFactor)
    //初始化容量 =   传进来的initialCapacity
//初始化负载因子= 传进来的loadFactor
       public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
    //只要看tableSizeFor方法这个方法最下面会有介绍,看方法名字是计算hash Table的的容量大小,这个方法是返回initialCapacity的2倍大小
        this.threshold = tableSizeFor(initialCapacity);
    }
   // 构造器介绍: public HashMap(int initialCapacity, float loadFactor) 




//构造器介绍:public HashMap(int initialCapacity)
//看方法是调用了上面的构造器
//初始化容量 =   传进来的initialCapacity,
//初始化负载因子= 默认0.74f
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
//构造器介绍:public HashMap(int initialCapacity)


//构造器介绍:无参数构造器
//初始化容量 =   默认的16,
//初始化负载因子= 默认0.74f
        public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
//构造器介绍:


//构造器介绍:参数是Map的构造器
//初始化容量 =   通过计算计算出能够存储 m这个Map的容量
//初始化负载因子= 默认0.74f
   public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
       //主要是这个方法,这个方法下面会介绍,看方法名字是往新的Map内放入数据
        putMapEntries(m, false);
    }
//构造器介绍


//tableSizeFor方法介绍
  /**
     * Returns a power of two size for the given target capacity.
     */
	//返回 2的整数次幂
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    //tableSizeFor方法介绍


//putMapEntries方法介绍

    /**
     * Implements Map.putAll and Map constructor
     *
     * @param m the map
     * @param evict false when initially constructing this map, else
     * true (relayed to method afterNodeInsertion).
     */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            
            //初始化 table的size,并计算扩容阈值  threshold
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }
//putMapEntries方法介绍
  • HashMap 的 put()方法
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }


    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
      //判断table是否初始化过,未初始化就进行第一次扩容并返回长度
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
      
      //判断当前hash所在索引的数据是否是空的,若是空就直接插入数据
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            
            //判断 key所计算出的hash值是否相同,相同就直接覆盖
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //判断当前的节点是否是红黑树节点,是就插入树节点
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //判断是否到达红黑树的阈值,如果到达了大于等于7就进行树化
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                   
    //未树化之前,如果key上存在值则跳过,赋值已经在if ((e = p.next) == null) 赋过值了
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            
            //返回key所在位置被覆盖的旧数据
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //HashMap是空实现,主要是在LinkHashM中重写使用
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
      //完成插入之后最后检查一次容量,若key长度大于阈值就进行扩容
        if (++size > threshold)
            resize();
      //HashMap是空实现,主要是在LinkHashM中重写使用
        afterNodeInsertion(evict);
        return null;
    }

//扩容函数
 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
     
     //非初始化执行
        if (oldCap > 0) {
            
            //如果旧的容量大小大于或等于默认最大值容量就返回最大容量
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //如果新的容量乘以2倍是在最大容量和最小容量之间,则返回旧容量的2倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
     //如果旧扩容阈值大于0,就初始化阈值
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
     
     //首次初始化执行
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
     //如果执行了初始化赋值后,扩容阈值还是为0,则在进行一次计算扩容阈值
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //如果e的下一个元素是空的,则往下一个元素的位置插入新的普通节点
                    if (e.next == null)
                        //重新计算每个元素存放位置
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果是树节点,那么超插入树节点
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //遍历链表
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

	//树化的方法
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //判断 需要被树化的 链表节点是否为空或长度是否小于最小容量,如果是则则进行扩容,如果不是则进行以下树化的步骤
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        
        //将普通的Node节点替换成TreeNoode
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

  • HashMap的线程安全问题
    1. jdk1.7之前,HashMap是采用数组+链表实现的,添加数据时是采用头插入法,在多线程插入数据时会产生 链表循环问题
    2. jdk1.8之后,HashMap是采用数组+链表+红黑树实现的,添加数据时是采用尾插入法,当链表长度大于等于8的时候转换成红黑树,小于等于6的时候又转换成链表,在多线程插入时候可能会产生数据覆盖
LinkHashMap介绍
  • 继承HashMap,和HashMap差不多

  • 数组+链表+红黑树实现,但是重写了HashMap的Node,但不同的是LinkHashMap保存上一个节点下一个节点

  • 注意有一个参数就是accessOrder ,若为true是顺序访问,那么LinkHashMap就会实现LRU算法集合不会扩容,若为false默认都是false,只有在使用这个构造器时才会

    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) 
    
集合常用操作
  • 需要注意的操作!!!!!
  • list.subList()方法会有以下问题
    1. subList()返回的是ArrayList的内部类SubList,不可以强转成ArrayList,否则会抛出ClassCastExceptio
    2. 对原集合进行添加,删除的操作时,subList的子列表会出现ConcurrentModificationException
    3. 对原集合修改后,subList子列表也会被相应改变
   //原集合
        List<Integer> data0 = new ArrayList<>();
        data0.add(1);
        data0.add(1);
        data0.add(1);
        data0.add(4);

        List<Integer> subList = data0.subList(0, data0.size()-1);

        //修改原集合data0,subList也会相应被修改,因为subList是浅开呗data0
        data0.set(0,10);
        for (Integer integer:subList) {
            System.out.println(integer);
        }

       // 对原集合data0进行添加,删除后,影响集合大小的操作
        // subList遍历、增加、删除 会抛出 ConcurrentModificationException
        data0.add(777);
        try {
            subList.set(0,99);
            //遍历
            for (Integer i:subList) {
                System.out.println(i);
            }

            //添加
            subList.add(66);

            //删除
            subList.remove(0);
        }catch ( ConcurrentModificationException   ce){
            System.out.println("出现ConcurrentModificationException");
        }
  • 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。

    1. 使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配内存空间,并返回新数组地址;
    2. 如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
      正例:
      List list = new ArrayList(2);
      list.add(“guan”);
      list.add(“bao”);
      String[] array = new String[list.size()];
      array = list.toArray(array);
      反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。
  • 使用工具类 Arrays.asList()把数组转换成集合时

    1. 不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
    2. asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。
      Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。
      String[] str = new String[] { “you”, “wu” };
      List list = Arrays.asList(str);
      第一种情况:list.add(“yangguanbao”); 运行时异常。
      第二种情况:str[0] = “gujin”; 那么 list.get(0)也会随之修改。
集合排序
  //原集合
        List<Integer> data0 = new ArrayList<>();
        data0.add(null);
        data0.add(null);
        data0.add(null);
        data0.add(1);
        data0.add(1);
        data0.add(1);
        data0.add(4);

        //底层调用 Arrays.sort,Arrays.sort是采用双轴快速排序
        data0.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1 == null || o2 == null) {
//                    throw new RuntimeException("参数异常");
                return 0;
                }
                if (o1 < o2) {
                    return -1;
                }
                if (o1 > o2) {
                    return 1;
                }
                return 0;
            }
        });

        for (Integer integer:data0) {
            System.out.println(integer);
        }

计算机基础

进制转换
1,十进制转二进制
  • 0到9
  • 十进制数150转二进制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eY0HPdWm-1620147205116)(/images/Java开发基础/十进制转二进制.jpg)]

2,二进制转十进制
  • 二进制数1001 0110转十进制
  • 从右到左乘以 2的幂数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IKURU7XH-1620147205117)(/images/Java开发基础/二进制转十进制.jpg)]

3.八进制转二进制
  • 0到7
  • 八进制数631转二进制
  • 每个数字转二进制,因为 8 = 2^3,所以每对二进制是3位数,不足3位在最左边补0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E6vywuTS-1620147205119)(/images/Java开发基础/八进制转二进制.jpg)]

4,二进制转八进制
  • 二进制数1010111转八进制
  • 从右向左,没3位组成一对,不足往左补0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dlqLOY27-1620147205120)(/images/Java开发基础/二进制转八进制.jpg)]

5,二进制转十六进制
  • 十六进制 从0到9,10到15 用字母A到F表示
  • 二进制1010111010转十六进制
  • 从右往左,取4合1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRROjs8B-1620147205121)(/images/Java开发基础/二进制转十六进制.jpg)]

6,十六进制转二进制
  • 十六进制数12C,取4合为一段二进制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zH6nwm3n-1620147205122)(/images/Java开发基础/十六进制转二进制.jpg)]

7,十进制转换成八进制,转换成十六进制或转换成其他进制
  • 间接法:先除2取余直到商为0,然后再由2进制转换为八进制十六进制或其他进制
  • 直接法:直接对相应进制取余知直到商为0为止
8,八进制转换为十进制,转换为十六进制
  • 八进制数,十六进制数按权展开,得到相应的十进制数
9,十六进制转换为八进制
  • 第一种,先转换为二进制再由二进制转换为相应进制数
  • 第二种,先转换为十进制再由十进制转换为相应进制数
10,总结
  • 十进制,八进制,十六进制转换为二进制,除以2取余数直到商为0为止,余数由下往上取(三个二进制数表示为一个八进制数,四个二进制数表示为一个十六进制数,不足位数的往最左边补0)
  • 二进制转换为十进制,八进制,十六进制,各进制数每个数字乘以2按权展开再相加(由右往左,二进制取三合一表示一个八进制数,二进制数取四合一 表示为一个十六进制数,十进制不用)
  • 十进制转八进制,十六进制,按相应进制数取余,除以8取余转八进制,除以16取余转十六进制,直到商为0
  • 八进制转十六进制,转二进制或转十进制,再转换为相应进制
二进制的简单加减乘除运算
  • 第一种,直接计算
//加法
第一种:从低位到高位依次相加,逢21,注意连续进位的情况
1100 1010 + 1111 = 1101 1001

//减法
第一种:从低位到高位依次相减,从高位借位,注意连续借位的情况
1100 1010 - 1111 = 1011 1011

//乘法
相乘偶数:左移1次为乘以22次为乘以4,依次类推。
相乘奇数:x=x+(x<<1);  x是一个二进制数
//除法
相除偶数:右移一次为除以二,依次类推
相除奇数:转化为十进制,进行计算然后再转化为二进制
  • 第二种方法:先将二进制数转换为十进制,用十进制进行运算,然后得出结果再转换为二进制
二进制如何表示小数
  • 为何Java中单精度类型数,双精度类型数的运算会出现不准确的情况?
// 0.8999999999999999
System.out.println(2-1.1);

//解析:
//首先2是int类型,但有1.1浮点数参与运,参与运算的值都转为long类型(long是16位二进制数保存的),所以最后结果是返回long类型,详情请参看运算符那一章
//实际计算的运算是以补码形式运算,而且是没有减法的,
// 2-1.1 = 2+(-1.1)
// 2 = 0000 0000 0000 0010(原码) = 0000 0000 0000 0010(反码)=0000 0000 0000 0010(补码)
// 1.1:先取出1,1=0000 0000 0000 0001(原码) = 0000 0000 0000 0001(反码)=0000 0000 0000 0001(补码),然后对0.1操作(将小数乘以2,直到小数为0为止,整数取出,若除不尽则按照Java中类型支持存储的长度或相应的规范进行保留)
0.1*2=0.2
0.2*2=0.4
0.4*2=0.8
0.8*2=1.9    //得1
0.9*2=1.8	//得1
0.8*2=1.6	//得1
0.6*2=1.2	//得1
0.2*2=0.4
。。。。。。	(一直循环下去直到小数为0,若除不尽则按照Java的规范保留多少位)
最后大概得出,-1.1 = 1000 0000 0000 0001.1111 1111 1111 1111(原码)=1000 0000 0000 0001.1111 1111 1111 1111(反码)=1000 0000 0000 0001.1111 1111 1111 1111(补码)

//得出运算过程式子,这个结果不是准确的
0000 0000 0000 0010 + 1000 0000 0000 0001.1111 1111 1111 1111得出十进制结果 0.8999999999999999
原码,反码,补码
  • 1个字节等于8位二进制数
  • 机器数:一个数在计算机中二进制形式保存,最高位是符号位,0表示的是正数,1表示的负数
  • 真值:机器数最高位是符号位,后面的值就是真值
  • 原码:正数就的原码是本身最高位是0其他位表示值,负数的原码最高位就是0其他位表示值
//假如用8位保存
10的原码:0000 1010
-10的原码:1000 1010
所以取值范围就是:1111 11110111 1111

//假如用16位存储
10的原码:0000 0000 0000 1010
-10的原码:1000 0000 0000 1010
所以取值范围的就是:1111 1111 1111 11110111 1111 1111 1111
  • 反码:正数的反码是本身,负数的反码就是负数原码的本身上符号位是1,其他位取反
//假如用8位存储
10的反码:0000 1010
-10的反码:1111 0101
  • 补码:计算机中实际存储的值正数的补码就是其本身,负数的补码是在其原码的基础上,符号位是1,其余各位取反,最后+1 (即在反码的基础上+1)。
10的补码:0000 1010
-10的补码:1111 010110符号位是1,原码基础上取反) + 1 = 1111 0101+11的二进制是1,二进制逢二进一)=1111 0110
  • 总结
    • 正数的原码,反码,补码都是一样的
    • 负数的最高位是1,正数的最高位是0
    • 负数的反码是在原码的基础上取反,负数的补码是在反码的基础上在最低位+1操作,最高位都是1
  • 知道一个数的补码如何求出这个数的原码?
  • 符号位是0,原码,反码,补码都是一样的
  • 符号位是1,那么补码的补码就是原码,注意符号位不变
Java中的位移运算
  • 首先求出这个数的补码,以下运算都是在补码上操作的,运算之后,算出结果的原码返回(注:负数补码的补是原码,符号位不变。正数的原码,反码,补码都是一样的)
  • 左移 运算,二进制数向左移动相应位数,舍弃高位,低位补0
  • 右移运算,二进制数向右移动相应位数,正数高位补0,舍弃低位,负数高位补1
  • 无符号右移运算(意思是不考虑符号位),二进数 向右移动相应位数,舍弃低位,高位补0
  • 与运算,两个二进制数对应的位置上都为1才是1,否则为0
  • 或运算,两个二进制数对应的位置上只要有1就为1,否则为0
  • 异或运算,两个二进制对应的位置上只要是相反的就为1,否则为0
  • 非运算,一个二进制数上对应操作位数上,只要是为1的结果是0,只要是为0的结果是1
Java位运算应用
  • 进制计算器,支持进制转换,加减乘除取模取余

网络基础

网络模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Bpt6ZvW-1620147205123)(/images/Java开发基础/网络模型.png)]

常见网络协议

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IrzhB9GD-1620147205124)(/images/Java开发基础/常见网络协议.png)]

常见协议对应端口号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M0oD8Ly9-1620147205125)(/images/Java开发基础/常见协议的端口号.png)]

以太网帧格式
  • 在以太网链路上的数据包称为以太帧

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zN9akvii-1620147205126)(/images/Java开发基础/以太网帧格式.png)]

IP协议的头部结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dVgkV9T6-1620147205127)(/images/Java开发基础/IP协议的头部结构.png)]

TCP协议的头部结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0CBBKdKv-1620147205128)(/images/Java开发基础/TCP协议的头部结构.png)]

UDP协议的头部结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nnzaowB5-1620147205129)(/images/Java开发基础/UDP协议的头部结构.png)]

什么是跨域问题?
  • 因为源地址与目标地址不同源,同源策略阻止了这一次请求
  • 协议+域名(ip)+端口这三个一致就表示同源,任何一个不一样就不同源
  • 同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击
  • 同源策略会限制以下行为
    1. Cookie,LocalStorage和IndexDB无法读取
    2. Dom对象和Js对象无法获取
    3. Ajax请求不能发送
如何解决跨域问题的?
  • JSONP解决
    1. JSONP 是服务器与客户端跨源通信的常用方法之一。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,所以一般我们也不用它
  • 通过代理层添加跨域解决,或者在服务器端配置跨域解决
    1. 如nginx反向代理
    2. Java的后端添加配置,access-control-allow-origin标头是主要的CORS头,一台服务器可以用它来显示什么允许的域
浏览器输入URL会发生什么事情?
  • 简单来说 首先会进行 url 解析,根据 dns 系统进行 ip 查找。
URL为什么需要进行解析(编码)?
  • 首先因为网络规定URL只能是字母和数字,所以需要对中文和特殊字符用UTF-8替换字符。编码可以进行调整,主要是要求请求和请求内容的编码集一致
  • 可以用encodeURIComponent和encodeURI对URL进行编码。encodeURIComponent编码范围更广适合参数编码,encodeURI更适合URL本身编码
DNS的解析过程

1、器中输入https://www.baidu.com 域名,操作系统会先查hosts件是否有记录,有的话就会把相对应映射的IP返回。

2、hosts文件没有就去查本地dns解析器有没有缓存。

3、然后就去找我们计算机上配置的dns服务器上有或者有缓存,就返回

4、还没有的话就去找根DNS服务器(全球13台,固定ip地址),然后判断.com域名是哪个服务器管理,如果无法解析,就查找.baidu.com服务器是否能解析,直到查到www.baidu.com的IP地址

注:后面查资料才发现dns查询有两种模式,一种是转发模式,一种是非转发模式,我上面说的4是非转发模式。

什么是TCP的三次握手,四次挥手?
  • 三次握手(进行连接)
    1. 第一次握手:主机A发送位码为SYN=1的TCP包给服务器,并且随机产生一个作为确认号(这是tcp包的一部分),主机B收到SYN码后直到A要求建立连接;
    2. 第二次握手:主机B收到请求后,向A发送确认号(主机A的seq+1),syn=1,seq = 随机数 的TCP包;
    3. 主机A收到后检查确认号是否正确,即第一次A发送的确认号是否+1了,以及位码ack是否为1,若正确,主机A会再发送确认号(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
  • 从TCP/IP的四层模型简单说一下连接过程
    1. 先从局域网把数据发送到公司的交换机(如果交换机没有缓存本地mac地址和IP地址的映射,此时会通过ARP协议来获得),交换机的好处是可以隔离冲突域(因为以太网用的是CSMA/CD协议,这个协议规定网线上同一时刻只能有一台机器发送数据),这样就可以不仅仅同一时刻只有一台机器发送网络包了
    2. 然后交换机再将数据发送到路由器,路由器相当于公司网关(我们公司小),路由器具有转发和分组数据包的功能(路由器通过选定的路由协议会构造出路由表,同时不定期的跟相邻路由器交换路由信息),然后这算是经过了物理层,数据链路层(以太网),开始到网络层进行数据转发了
    3. 然后路由器转发IP数据报,一般公司的IP地址都会经过NAT转换,让内网的ip也能够访问外网,我们公司我注意了一下是192.168打头的内网ip地址。通过路由器的分组传输,所有数据到达服务器。
    4. 然后服务器的上层协议传输层协议开始发挥作用,根据tcp包里的端口号,让服务器特定的服务来处理到来的数据包,并且tcp是面向字节流的(tcp有四大特性,可靠传输、流量控制、拥塞控制、连接管理),所以我们node的request对象,它的监听事件data事件为什么要用字符串一起拼接起来呢(buffer),就是因为tcp本身就是字节流,request对象使用的data(http层面)是tcp传来的数据块。
    5. 最后数据由传输层转交给应用层,也就是http服务(或者https),后端经过一系列逻辑处理,返回给前端数据。
  • 四次挥手(断开连接)
  • 关闭客户端到服务器的连接:首先客户端 A 发送一个 FIN,用来关闭客户到服务器的数据传送,然后等待服务器的确认。其中终止标志位 FIN=1,序列号 seq=u
  • 服务器收到这个 FIN,它发回一个 ACK,确认号 ack 为收到的序号加 1
  • 关闭服务器到客户端的连接:也是发送一个 FIN 给客户端。
  • 客户段收到 FIN 后,并发回一个 ACK 报文确认,并将确认序号 seq 设置为收到序号加 1。
  • 首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
HTTP的状态码

状态码 状态码英文名称 中文描述
100 Continue 继续,客户端应继续其请求
101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议
200 OK 请求成功。一般用于GET与POST请求
201 Created 已创建。成功请求并创建了新的资源
202 Accepted 已接受。已经接受请求,但未处理完成
203 Non-Authoritative Information 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
204 No Content 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205 Reset Content 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域
206 Partial Content 部分内容。服务器成功处理了部分GET请求
300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 See Other 查看其它地址。与301类似。使用GET和POST请求查看
304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy 使用代理。所请求的资源必须通过代理访问
306 Unused 已经被废弃的HTTP状态码
307 Temporary Redirect 临时重定向。与302类似。使用GET请求重定向
400 Bad Request 客户端请求的语法错误,服务器无法理解
401 Unauthorized 请求要求用户的身份认证
402 Payment Required 保留,将来使用
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405 Method Not Allowed 客户端请求中的方法被禁止
406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求
407 Proxy Authentication Required 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权
408 Request Time-out 服务器等待客户端发送的请求时间过长,超时
409 Conflict 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突
410 Gone 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置
411 Length Required 服务器无法处理客户端发送的不带Content-Length的请求信息
412 Precondition Failed 客户端请求信息的先决条件错误
413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息
414 Request-URI Too Large 请求的URI过长(URI通常为网址),服务器无法处理
415 Unsupported Media Type 服务器无法处理请求附带的媒体格式
416 Requested range not satisfiable 客户端请求的范围无效
417 Expectation Failed 服务器无法满足Expect的请求头信息
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
502 Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version not supported 服务器不支持请求的HTTP协议的版本,无法完成处理

请求头参数介绍

Header 解释 示例
Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html,application/json
Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5
Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip
Accept-Language 浏览器可接受的语言 Accept-Language: en,zh
Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes
Authorization HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) Connection: close
Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Cookie: $Version=1; Skin=new;
Content-Length 请求的内容长度 Content-Length: 348
Content-Type 请求的与实体对应的MIME信息 Content-Type: application/x-www-form-urlencoded
Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect 请求的特定的服务器行为 Expect: 100-continue
From 发出请求的用户的Email From: user@email.com
Host 指定请求的服务器的域名和端口号 Host: www.zcmhi.com
If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10
Pragma 用来包含实现特定的指令 Pragma: no-cache
Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range 只请求实体的一部分,指定范围 Range: bytes=500-999
Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: http://www.zcmhi.com/archives…
TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5
Upgrade 向服务器指定某种传输协议以便服务器进行转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent User-Agent的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11)
Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning

CDN的原理是什么?
  • 最简单的理解,输入URL,通过专门的CDN的DNS解析服务器,通过就近原则和解析器的状态,返回离用户最近的IP访问地址

加密算法

  • MD5:单向加密,无法解密,但可以通过MD5的明文字典暴力解密
  • AES:对称加密,加密与解密的密钥都是一样的
  • RSA:非对称加密,加密与解密的密钥是不一样的

程序设计相关

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值