JAVA学习日记

此博客编写初衷:本人从事java工作已经3年(18岁培训班毕业,自考了大专/本科),20年疫情期间找工作艰难,感到自己java基础不够扎实,框架只会固定的SSM,固写一篇博客来鞭策自己并记录自己的学习日志.大致方向如下:
1.java基础(集合,多线程,JVM虚拟机)
2.常见设计模式(代理模式/工厂模式/单例模式等)
3.分布式框架(springboot,springcloud大部分组件,rabbitmq,如时间够还有dubbo,zukker)
4.redis

参考博客为:java知识体系最强总结
感谢该大神!
如以上全部学习完成,再写一个简单的简历展示系统,大致功能模块如下(可能还需自学前端框架):
1.登录注册模块
2.简历模块.
3.邮箱发送模块.
4.后台管理模块
技术点:登录采用redis缓存,注册时要用邮箱发送模块发送验证码(rabbitmq),后台管理模块则用到redis缓存单点登录,只能我一个特定的用户能登进去查看.

4月10日学习记录

1.java的反射

1 java的反射是指在程序运行期间可以根据任意实例对象获取对应的类,然后根据对应
的类获取并调用该类的方法/成员变量/新建对象.
2 反射四大类:Class(类)/Method(方法)/Field(成员变量)/Constructor(构造器)
3 暴力反射是指可以调用类的私有方法,需要调用setAccessible(true)方法

2.java的集合框架以及区别(框架源码就自己看自己理解,就不写上来了)

1 java的集合是用来存储对象数据的,java数组也可用于存储数据,两者的区别是
	1.1 数组能存储基本数据类型和引用类型,集合只能存储引用类型
	1.2 数组的长度是固定的,集合的长度是可变的.
	1.3 数组只能存放一个类型的数据,集合可以存放不同类型的数据
2 集合分为两大接口Collection和Map(详细见下图)
	2.1 Collection主要分为Queen/List/Set,List接口的实现类主要分为Arraylist/LinkedList/Vector
	Set接口的主要实现类为HashSet/TreeSet/LinkedHashSet(List有序,可以重复。Set无序,不可以重复)
	2.2 Map接口的实现类主要分为HashMap/HashTable/LinkedHashMap/ConcurrentHashMap/TreeMap(键值对)
3 ArrayList是底层是由数组实现,每次自动扩容为当前长度的1.5倍,随机查找快,增删慢(因为数组增加删
	除都要拷贝.),Vector扩容长度是当前长度的2倍,是线程安全的,所以Vector效率比ArrayList会低
4 LinkedList底层是双向链表结构实现的,相较ArrayList来说增删快,随机查找慢,也是线程不安全的.
5 TreeSet底层是红黑树(这个算法不是很懂,只知道是二叉树的加强版,避免单节点过长),不支持NULL值,会自动排序
6 HashSet底层是HashMap(就是HashMap的键算法,值都是PERSENT),支持NULL值,无序.
      当add一个值时,会先计算值的hashCode,如果有相同的hashCode值,再比较equals()方法.所以重写equals一定要重写hashcode
7 HashMap底层是链表+数组+红黑树实现,线程不安全,可以添加NULL,扩容长度是2倍.
8 TreeMap底层是红黑树实现,线程不安全,Key如果未实现对NULL的判断则不允许未空.
9 HashTable底层是链表+数组,线程安全,不允许Key为NULL,扩容长度是2倍+1
10 LinkedHashMap底层是双向链表结构
11 ConcurrentHashMap是线程安全的HashMap(比hashtable效率高,因为使用的是分段锁),将hash表分为16个部分
	并分段加锁,每个部分的添加删除都可由不同的线程同时操作.
12 HASHMAP添加键值对步骤,先根据键的HASHCODE判断要放到数组哪个位置,如果table[i]已有值,
	判断首个元素的值是否和键相等(HASHCODE和equals),相等则直接覆盖,否则判断是否为红黑树,
	是红黑树就直接放入,不是再判断长度是否大于8,大于8就要转换成红黑树,否则插入

在这里插入图片描述

3.jvm内存模型理解

	1 jvm虚拟机相当于一个小型的计算机,线程就相当于计算机中的cpu,每个线程都有自己的本地变量保存的地方
		(相当于cpu缓存,称为工作内存),如果一个线程想与另一个线程共享一个资源,这个资源就存放在主内存中(可
		通过该资源实现线程互相通信等).一个线程要修改主内存中的资源时会先修改自己工作内存中的资源然后再
		同步到主内存.这就涉及到java的多线程安全问题了.(即两个线程同时修改一个公共变量,可能会造成数据
		冲突,解决该问题的方式为synchronized/Lock/volatile).
	2 一个线程对一个变量的操作分为8个步骤
		lock(加锁):给主内存中的变量上锁,防止其他线程占用
		unlock(解锁):与上相反
		read(读取):将变量从主内存读取到工作内存
		load(加载):将读取到的变量放入工作内存中的变量副本
		use(使用):使用工作内存中的变量
		assign(赋值):将执行引擎读取到的值赋给工作内存中的变量
		store(存储):将工作内存中的变量传递给主内存
		write(写入):将读取到的工作内存中的变量赋给主内存中的变量
	3 原子性/可见性/有序性(有序性是因为编译器会发生指令重排)

**其实jvm内存模型就和我们利用多线程开发差不多,内存屏障这块有点不能理解(等脑子静下来再看)

4.java线程的创建方式

1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
4.使用线程池工具类Executors创建

5.线程的生命周期

1.创建状态(new一个线程时表示一个线程已经创建好了)
2.就绪状态(调用线程的start()方法后表示该线程已经准备好等待cpu的调用)
3.运行状态(cpu调用该线程执行线程中的run()方法)
4.阻塞状态(当线程处于等待状态时,比如调用sleep/wait()方法,或等待获取对象锁时)
5.死亡状态(线程执行完毕或因为异常中断导致线程关闭)

6.ArrayBlockingQueue与LinkedBlockingQueue(这个理解了RabbitMq也好理解)

1.前者和后者的相同点是都用了condition通知机制来实现可阻塞式插入和删除元素.
2.不同点是前者是数组后者是链表且后者put()与take()方法用了两个锁,可以实现2个线程同时插入删除.

7.JVM虚拟机

1.jvm由4个部分组成:类加载器/运行时数据区/执行引擎/本地库接口.4者之间的关系为
	先由编译器将java文件解析成字节码文件,然后由类加载器加载对应的类到运行时数据区,
	然后执行引擎负责将运行时数据区的类翻译成windows或linux能运行的命令并执行(该过程中要使用本地库接口)
2.运行时数据区又分为:堆/方法区/虚拟机栈/本地方法栈/程序计数器(堆与方法区是线程共有的).
	堆存放的一般是各种对象的实例.
	方法区存放的一般是各种类信息,静态方法,静态变量等
	虚拟机栈存放的一般是java方法执行信息:方法中的成员变量、执行结果等.
	本地方法栈与虚拟机栈一样,不过存放的是调用本地方法的信息.
	程序计数器存放的是当前线程执行到了字节码文件的哪一行信息.
3.GC垃圾回收机制都在堆中执行,堆分为新生代/老年代。
	新生代又分为eden/serivceto/servicefrom,一般新建的对象都分配在新生代(大的放在老年代)
	新生代采用标记-复制算法,每发生一次minorGC就会将eden和serviceto存活的对象移动到servicefrom.
	然后将对象年龄+1并调换serivceto/servicefrom位置.
	老年代采用标记-整理算法回收:majorgc发生前都会发生一次minorgc,minorgc发生后发现老年代空间
	不足就会引发majorgc.如果还是装不下就会发生outofmemory异常。
4.类加载过程:
	加载:根据传入的路径导入对应的文件
	验证:验证文件中的代码是否正确.
	准备:为类中的变量,方法开辟内存空间并设置初始值.
	解析:将常量池中的符号引用变为直接引用
	初始化:给类中的静态变量,赋值.
5.类加载器:启动类加载器,扩展类加载器,应用程序类加载器,自定义加载器。双亲委派模型

8.Redis相关(java程序使用jedis来操作Redis)

1.redis是一种读写很快(数据存放在内存中)的nosql数据库(键值对),实际应用场景可用于缓存(用户登录)/
	分布式锁(对某些公共方法或数据的调用)/抽奖(set)/活跃用户统计(setbit 位图计算)
2.redis有5种数据类型(String/List/Zset/Set/Hash),
3.redis默认的持久化叫RDB(可设置定时将内存中的数据存储到硬盘,缺点是可能造成数据丢失很多)
	还要一种持久化叫AOF可以设置成每秒或每次操作都存储一次数据.(缺点是读写频繁性能不好)
4.redis可以设置数据过期时间,对过期的数据采用删除策略:立即删除(对cpu不好)/惰性删除(对内存不好)/
	定时删除(折中方案)
5.内存淘汰策略:
	5.1 拒绝写入新的数据
	5.2 移除最近使用最少的数据
	5.3 随机移除数据
	5.4 从过期数据中移除最少使用的数据
	5.5 从过期数据中移除即将过期的数据
	5.6 从过期数据中随机移除
6.redis Cluster集群,最多可有16384个节点(主节点异步同步数据到从节点)

阿里云redis使用规范

4月11日学习记录

1.设计模式

1.单例模式的实现要点是:构造方法私有化且只有一个对象能存在(饿汉式和懒汉式,懒汉式要加锁不然会线程不安全)
2.工厂模式的实现要点是:要有一个工厂类专门负责类的创建(这些被创建的类都要实现同一个接口)
3.代理模式的实现要点是:代理类自己的方法中要能调用到被代理的类的方法.

经典设计模式活学活用

2.spring

1.spring是一个轻量级的框架,核心是IOC容器和AOP模块.
	IOC的作用是管理类(创建/销毁生命周期)和类与类之间的依赖,主要是通过工厂模式BeanFactory和java反射
	机制实现的.
	AOP又叫做面向切面,其实就是加强的代理模式,主要作用是事务管理/权限拦截等.(有两种代理模式JDK动态代
	理和CGLIB代理,前者实现方式为实现InvocationHandler接口并使用Proxy类获取真正的实例对象完成代理.后
	者实现方式为生成一个代理类的子类.也就是被代理类必须是可被继承的)
2.springbean的生命周期依次如下:
	初始化(即new)->注入属性->setBeanname->setBeanFactory->setApplicationcontext->
	postProcessorbeforInit->afterpropteryset->postprocessorafterinit-销毁
3.spring bean的模式有单例(singleton)/多例(prototype)/请求(request)/会话(session)
4.spring事务传播特性有:
	require:如果当前有事务,就加入,没有就新建一个事务.
	support:如果当前有事务,就加入,没有就以非事务方式执行
	mandatory:如果当前没有事务就报错.
	new:当前有没有事务都新建一个事务.
	never:当前有事务就报错
	not support:如果当前有事务,就将当前事务挂起
	netst:如果当前有事务,就自己再新建一个事务(嵌套事务),没有的话就按require方式执行
5.springMVC的请求生命周期如下:
	进入DispatherServlet->根据uri到处理器适配器(HandlerMapping)中找到对应的处理器控制器(HandlerAda
	pter)->处理器控制器返回对应的处理器(Controller)->调用处理器返回具体的ModelAndView->交给View
	Resolver渲染成视图返回给用户.

3.spring boot

1.springboot是一个敏捷开发的框架,他是不用写很多xml配置的spring项目。
2.springboot jar包中集成了tomcat,可以直接以运行jar包的方式启动项目.
3.spring中的很多xml配置信息可以写在springboot项目的application.yml配置文件里面(比如数据源)
4.springboot核心注解是@SpringbootApplication,这个注解是用于一个启动类,集成了3个注解的功能:
	@ComponentScan:自动扫描Bean
	@EnableAutoConfiguration:自动配置
	@SpringBootConfiguration:实现了配置文件的功能(就是用.java替代spring xml文件配置)
5.springboot的starter说白了就是很多框架提供的配合springboot开发的jar包,这些jar包中一般都有一个
	java配置类,这个配置类提供一系列的默认配置,springboot会因为引入了jar包然后自动扫描这些配置类

4.分布式概念

1.传统项目中所有的功能都是由一个程序提供的,比方说一个淘宝程序,提供用户模块/商品模块/购物车模块等等
	分布式就是将这些模块分成不同的程序,比如一个用户程序/商品程序/购物车程序.当用户需要用到商品功能
	就会去请求商品程序,用到登录注册就回去请求用户程序.(分布式是不同的服务提供不同的功能,集群是相同的
	服务相同的功能,防止集群中的主服务挂掉导致功能提供不了)
2.一个分布式项目中存在很多个微服务提供功能,这些微服务之间也会相互调用,就需要一个注册中心去管理
	这些微服务.(比如Eureka或Zookeeper ),每个微服务都告诉注册中心自己的端口和自己的功能,用户请
	求发送到注册中心,然后注册中心识别请求将请求转发到对应的微服务项目上.
3.分布式框架一般是(dubbo+Zookeeper )或(spring cloud+Eureka),因为只用过springboot,看网上的描述
	好像是前者使用难度更大一点,但是从性能来说会稍微好一点.(因为前者是用二进制传输数据后者是用
	httprest风格).

5.分布式单点登录流程

	有3个服务:登录中心/服务1/服务2。
	1.用户首先请求服务1
	2.服务1未发现用户带有token,就跳转到登录中心的页面让用户登录
	3.登录中心获取用户cookie发现用户并未登录过,用户在登录中心输入用户名密码登录后,登录中心再给用户一个
	token(存放在cookie中,过期时间半小时),并将该token与用户id以键值对形式存放在redis缓存中,设置一个过
	期时间半小时.然后将该token返回给服务1.
	4.服务1收到登录中心返回的token,再次拿该token去登录中心对比,如果有的话登录中心返回给服务1一个正确
	应答
	5.服务1收到正确应答后也与该用户存放一个表示登录成功的cookie,即该用户在cookie存在的这段时间中再次
	访问服务1不再需要去登录中心认证.
	6.用户访问服务2,服务2发现与用户之间没有表示登录成功的cookie,又跳转到登录中心,登录中心获取cookie
	发现该用户已经登录过一次,即返回给服务2一个token,服务2再次重复服务1的(4/5)动作

4月12日学习记录

1.创建springcloud项目

1.静态页面访问不到js/css/img等资源.找了半天发现是因为MAVEN打包的目录下没有那些文件.
	重新clean打包一下就好了
2.使用mybatis时,需要在启动类上加上@MapperScan("mapper所在包名").
3.springboot使用redis只需要引入相关jar包,做好配置,然后调用StringRedisTemplate类就可简单插入/删除
	https://blog.csdn.net/weixin_43835717/article/details/92802040这是参考链接(我们
	是大自然的搬运工哈哈)

4月13日学习记录

1.spring cloud分布式中的服务提供方和服务消费方其实就是传统的HTTP方式相互调用,只是因为有了注册中心
	可以实现服务消费方动态获取服务提供方的地址(同一个服务可能有2个甚至多个服务提供方)
2.服务提供方需要配置一个CorsConfig实现WebMvcConfigurer接口解决跨域问题(规定有哪些域名可以访问)
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .maxAge(3600);
    }

}
3.调用服务有2种方式,参考链接https://segmentfault.com/a/1190000018531262,写得很详细
4.做单点登录功能发现了一个spring-session工具可以配合redis实现不同服务之间的session共享
	只需要在启动类上加一个@EnableRedisHttpSession注解即可!!不过一天也没白忙活,最起码
	知道了微服务之间相互调用的方式.(后续可能会用dobbo也做一个)

4月14日学习记录

RabbitMq学习

1.RabbitMq就是一个消息中间件,介于服务之间.常见应用场景(注册异步发送邮件/流量削锋(超过多少条消息
就拒绝接受消息)/解耦(如果某个系统挂掉,消息不会丢失,会存放在消息中间件中))
2.RabbitMq有多种工作模式:
	简单模式:一个生产者对应一个消费者
	工作模式:一个生产者对应多个消费者(轮询分发和公平分发.前者是指每个消费者接收到的消息数量都
		一样,后者是指根据消费者处理消息的能力来接收消息数量)
	订阅模式:一个生产者对应一个交换机(Exchange),一个交换机对应多个队列(队列声明时绑定到交换机
		上),每个消费者都对应自己要对应的队列.生产者发送消息给交换机,交换机会把消息推送到绑定的
		队列
	路由模式:发送消息给交换机时会有一个关键词(routingkey),如果指定了这个routingkey,那么只有
		绑定了该routingkey的队列才能获取到消息.(也就是发布订阅时可以根据用户类型的不同来发消息)
	主题模式:和路由模式差不多,但是可以用通配符(也就是queen绑定的时候可以绑定某一个类型的routingkey)
3.RabbitMq不允许重新定义一个已经存在的队列,声明队列的时候可以将队列设置成持久化.(durable属性=true)
4.RabbitMq也提供事务机制保证生产者的消息成功发送到rabbit(可以是同步也可以是异步.看channel怎么设置)

学习视频地址:RabbitMq入门
集成SpringBoot地址:SpringBoot集成RabbitMq

4月18日学习记录、

前几天被目前的市场行情搞蒙了,都不想学Java了,但是还是坚持下去吧
今天看一下Arraylist的源码.

1.Arraylist

Arraylist有3个构造函数.
1.无参构造函数:

  //无参构造函数,Arraylist底层实际上是一个数组,实例化一个无参构造函数时,默认会给一个空的数组
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

2.传递一个int参数的构造函数

//这个int的含义实际上就是指定arraylist的创建时的长度.
//实际场景是你要存储的数据你已经知道大概的数量会很多,那么指定一个长度可以避免添加时自动扩容(增加效率)
//以空间换时间
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    public ArrayList(int initialCapacity) {
    	//大于0就创建一个长度为initialCapacity的object数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        //等于0就创建一个空的数组,其实就相当于无参构造函数
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
        //小于0就抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    

3.传递一个Collection参数的构造函数:

	//传递一个现成的集合对象并将他转换成Arraylist.
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        //如果传递进来的集合对象长度不为0且不是Object数组。
        //就将其转换成Object数组
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

以上是三个构造函数,接下来看一下Arraylist的add()方法:
可以很明显看到size是当前数组中最后一位元素的位置,elementData[size++] = e;就是将新元素赋值给数组的下一位,那么再看一下ensureCapacityInternal(size + 1)函数

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

这个判断很明显是表示如果当前数组是空的,就给当前元素的最小容量设置成10(前提是传进来的参数不大于10),再看一下ensureExplicitCapacity(minCapacity)函数:

    private static final int DEFAULT_CAPACITY = 10;
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

modcount++先不管,看下面一行的意思是如果目前的数组长度不够,就扩容,扩容的规则看一下

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

先把数组的长度赋给oldCapacity,
然后新的数组长度等于老数组长度的1.5倍
如果新的数组容量newCapacity小于传入的参数要求的最小容量minCapacity,那么新的数组容量以传入的容量参数为准
如果新的数组容量newCapacity大于数组能容纳的最大元素个数 MAX_ARRAY_SIZE 2^{31}-1-8
那么再判断传入的参数minCapacity是否大于MAX_ARRAY_SIZE,如果minCapacity大于MAX_ARRAY_SIZE,那么newCapacity等于Integer.MAX_VALUE,否则newCapacity等于MAX_ARRAY_SIZE

 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值