Java基础总结

一,Java开发部分问题

1.异常,log4j日志和 static final 修饰符

博客:Java自定义异常(优雅的处理异常)

自定义异常:自带的异常可能满足不了我们业务的需求, 这个时候我们可以自定义异常来进行处理。
通常情况下,只需要在控制层最外层try异常,处理异常:记录日志,跳转500页面等…
服务层,持久层,其他调用方法如果异常,尽管抛出异常

/**
 * 
 * @Description: 自定义使用异常,重写Exception构造方法即可
 */
public class CustomUseException extends Exception{
}
/**
 * 执行
 */
public boolean run(Map<String,String> planMap) {
	try {
		//任务模板id
		String modelId = planMap.get("modelId");
		if (!StringUtils.isBlank(modelId)) {
			//todo
		}
		else {
		 	//使用throw 抛出创建的异常
			throw new CustomUseException("错误:计划程序没有定义模板");
		}
		
	} catch (Exception e) {
		//记录日志一定要明确关键信息,使发生异常时能快速定位错误来源,原因
		//栗子1:此方法用于执行计划程序,计划程序id,名称则是关键信息。
		//发生异常时,一看异常描述就能定位是那个计划程序
		//栗子2:如果是修改用户信息,用户名称,id则是关键信息
		LOG.error("来源:计划程序名称="+planMap.get("name")+",ID="+planMap.get("id"), e);
	}
	
	return true;
}

异常分类

  • 1.由于用户行为导致的异常(没有通过验证器,没有查询到结果)
    通常不需要记录日志,需要向用户返回信息(比如:登录密码错误,删除系统管理员等)
  • 2.服务器自身异常(代码错误,调用外部接口错误)
    必须记录日志,同时响应前台:可以跳转500页面,或给出提示信息
	/**
	 * 静态LOG对象,定义成static final,logger变量不可变,读取速度快
	 * static修饰变量是类变量,不管new了多少个实例,也只创建一次
	 * 一般没有应用到实例的方法或者变量都要求定义成static,避免内存不断被分配和回收
	 * 不做修改的变量用final修饰符,访问控制符private根据实际情况定义,LOG对象只需在本类使用
	 */
	private static final Logger LOG = Logger.getLogger(DianmingPlan.class);
	
	/**
	 * 共有配置可以提取到.properties配置文件,利于后期维护
	 * 共有变量可以提取成一个常量类Constant,使用public static final定义
	 * 访问控制符public根据实际情况定义,这里需要都能访问
	 */
	public static final String PRJ_SOURCE = SysProp.getProp("prj_source"); 		
	try{
		//dothing
		//记录debug级别的信息,debug信息通常用于开发者调试
	    //LOG.debug("");
	    //记录info级别的信息
	    //LOG.info("");
	    //记录error级别的信息
	    //LOG.error(e.getMessage(), e);
	}catch(SQLException sqlex){
		LOG.error("绑定QQ失败!",sqlex);
		throw new Exception("绑定QQ失败,请重试!"+sqlex.getMessage() );
	}

以下配置参考博客:log4j.properties配置使用详解

#配置根Logger:level,appenderName1,appenderName2
#level是日志级别,Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG
#定义了INFO级别,则应用程序中所有DEBUG级别日志信息将不被打印
#appenderName是日志信息输出位置,可以同时指定多个输出目的地
log4j.rootLogger=INFO,consoleApender,LogFileApender

#appender有以下5种,分别可以将日志信息输出到5个不同的平台
#org.apache.log4j.ConsoleAppender(控制台)
#org.apache.log4j.FileAppender(文件)
#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
#org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
#org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)

#日志信息输出到控制台
log4j.appender.consoleApender=org.apache.log4j.ConsoleAppender   
#输出信息的格式,ConversionPattern=%m%n:指定怎样格式化指定的消息
log4j.appender.consoleApender.layout=org.apache.log4j.PatternLayout    
log4j.appender.consoleApender.layout.ConversionPattern=[%p][%d{yy/MM/dd HH:mm:ss}]%m%n
#指定日志消息的输出最低层次
log4j.appender.consoleApender.Threshold=DEBUG

#日志信息输出到文件
log4j.appender.LogFileApender=org.apache.log4j.DailyRollingFileAppender   
#保存log的文件路径
log4j.appender.LogFileApender.File=${catalina.base}/pm_logs/pm.log
#默认 true,添加到末尾,false在每次启动时进行覆盖
log4j.appender.LogFileApender.Append=true
#单个log文件大小,超过大小就又会生成 1个日志,单位:KB,MB,GB
log4j.appender.LogFileApender.MaxFileSize=10MB
#输出信息的格式,ConversionPattern=%m%n:指定怎样格式化指定的消息
log4j.appender.LogFileApender.layout=org.apache.log4j.PatternLayout
log4j.appender.LogFileApender.layout.ConversionPattern=[spdd-run] %-d{yyyy-MM-dd HH\:mm\:ss} %p [%t] %c{1}.%M(%L) | %m%n 
#指定日志消息的输出最低层次
log4j.appender.LogFileApender.Threshold=DEBUG


#debug模式下输出sql  
log4j.logger.java.sql.ResultSet=DEBUG       

2.单例模式

单例模式(确保实例只有一个,尤其是多线程环境),参考博客:静态内部类单例原理
单例模式有饿汉模式、懒汉模式、双重锁懒汉模式、静态内部类模式、枚举模式。
推荐使用静态内部类模式:

/**
 * 单例之静态内部类模式
 * 类级内部类指有static修饰的成员内部类,如果没有static修饰的成员式内部类被称为对象级内部类,
 * 类级内部类相当于外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建,而对象级内部类的实例,是绑定在外部对象实例中的
 * 类级内部类相当于外部类的成员,只有在第一次被使用的时候才会被装载。
 */
public class SingleTon {
	/**
	 * 1.私有构造方法
	 */
	private SingleTon() { 
	}
	/**
	 * 静态内部类的优点是:
	 * 1.外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
	 * 2.只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,虚拟机才会加载SingleTonHoler类
	 * 3.不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化
	 */
	private static class SingleTonHoler {
		private static SingleTon INSTANCE = new SingleTon();
	}
	/**
	 * 2.以静态方法返回实例
	 * @return
	 */
	public static SingleTon getInstance() {
		return SingleTonHoler.INSTANCE;
	}
}

3.泛型

泛型,即“参数化类型”,作用:通过泛型指定的类型来控制形参具体限制的类型
提到泛型,用到最多的就是在集合中, 在实际的编程过程中,
可以使用泛型去简化开发,且能很好的保证代码质量。

参考博客:java 泛型详解
1.泛型通配符,<?>
2.泛型方法与可变参数,public void printMsg( T… args)
3.泛型上下边界传入的类型实参必须是指定类型的子类型,
Generic<? extends Number> obj 或 Generic

泛型方法总结:

  • 无论何时,尽量使用泛型方法,使用泛型方法将整个类泛型化,
  • 静态方法无法访问类上定义的泛型,如果static方法要使用泛型,就必须声明成为泛型方法。

例子:

public final class InstanceFactory {
	/**
	 * 日志
	 */
	private static Logger LOG = Logger.getLogger(InstanceFactory.class);
	
	private InstanceFactory(){}
	
	/**
	 * 采用饿汉式单例INSTANCE_MAP对象,作为实例对象的容器
	 * 类似于Spring的BeanFactroy思想,采用的是延迟加载形式来注入Bean,以达到单例和复用实例对象
	 */
	private static final HashMap<String, Object> INSTANCE_MAP = new HashMap<>();
	
	/**
	 * 说明:public与返回值中间<E>非常重要,可以理解为声明此方法为泛型方法。
	 * 1.只有声明了<E>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
	 * 2.<E>表明该方法将使用泛型类型E,此时才可以在方法中使用泛型类型E。
	 * 3.与泛型类的定义一样,此处E可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
	 * Java泛型中的标记符含义: 
	 * 	E - Element (在集合中使用,因为集合中存放的是元素)
	 * 	T - Type(Java 类)
	 *  K - Key(键),V - Value(值)
	 *  N - Number(数值类型)
	 *  ? -  表示不确定的java类型
	 */
	@SuppressWarnings("unchecked")
	public static <E> E  getInstance(Class<E> clazz)  {
		if( INSTANCE_MAP .get(clazz.getName()) != null) {
			return (E) INSTANCE_MAP .get(clazz.getName()) ;
		}
		synchronized (LOG) {
			E instance = null;
			try {
				instance = clazz.newInstance();
				INSTANCE_MAP.put(clazz.getName(), instance);
			} catch ( InstantiationException | IllegalAccessException e) {
				LOG.error("[InstanceFactory] 实例化初始失败!"+clazz, e);
			} 
			return instance;
		}
	} 
}

4.反射

对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
在运行时期,每个类只会初始化一次,生产一个class对象

4.1获取class对象的三种方式
1)Object.getClass()
2) 任何数据类型(包括基本数据类型)都有一个静态的class属性
3) Class类的ForName(String name) 方法,这个常用,因为不用导包,只需要传入包名+类名

4.2通过反射获取构造,成员方法,属性
4.3反射的作用及使用场景

  • 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
  • 自定义注解,注解就是在运行时利用反射机制来获取的。
  • 在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射。

4.4性能分析
反射机制是一种程序自我分析的能力。用于获取一个类的类变量,构造函数,方法,修饰符。
优点:运行期类型的判断,动态类加载,动态代理使用反射。
缺点:性能问题,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接java的代码要慢很多。

try {
	//全路径类名获取Class对象
	Class<?> c = Class.forName(clazz);
	//获得类中声明的公开方法,如下获取无参Method对象
	//参数1是方法名,参数2是按声明顺序标识的方法形参类型
	Method md = c.getMethod( taskApiBean.getMethodName() ) ;
	Thread run = new  Thread() {
		public void run() {
			//任务需要延时
			if(taskApiBean.ge1tApi().waitTime()>0) {
				try {
					Thread.sleep(taskApiBean.ge1tApi().waitTime()*1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			try {
				//如果是静态方法
				if( Modifier.isStatic(md.getModifiers()) ) {
					//Object obj, Object... args
					//参数1实例化后的对象,参数2用于方法调用的参数
					//调用静态方法Object传入null
					md. invoke( null );
				}
				//如果非静态方法
				else {
					//根据实例对象调用无参非静态的方法
					md. invoke( InstanceFactory.getInstance(c) );
				}
			} catch (Throwable e) {
				SysLog.printDebug("线程反射调用错误:"+clazz+"-"+taskApiBean.getMethodName()+",错误信息:"+e.getMessage());
			}
			
		}
	};
	openThread(run, clazz + "-" + taskApiBean.getMethodName() , taskApiBean.ge1tApi().autoOpen() );
	SysLog.printDebug(taskApiBean.getMethodName()+" 开启任务成功!");
}catch (Throwable e) {
	SysLog.printDebug( taskApiBean.getMethodName()+" 开启任务失败!"+e.getMessage() );
}

5.过滤器与拦截器

5.1Filter的生命周期
init()方法:初始化参数,在创建Filter时自动调用,init方法也只会执行一次。
doFilter()方法:拦截到要执行的请求时,doFilter就会执行。对请求和响应的预处理,可以执行多次。
destroy()方法:在销毁Filter时自动调用。

@WebFilter("/*")
public class ACharacterEncodingFilter implements Filter{

	@Override
	public void destroy() {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		//放行
		chain.doFilter(request, response);
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
	}
}

5.2Java实现拦截器,依赖的技术就是 Java 的动态代理
Java原生与Spring框架不同,
Spring中实现HandlerInterceptor即可实现拦截器,preHandle方法在controller调用之前执行

总结:过滤器包裹住servlet,servlet包裹住拦截器
拦截器与过滤器的区别?
1、拦截器不依赖于servlet容器,过滤器的运行必须要容器的支持
2、拦截器是基于java的反射机制的,而过滤器是基于函数回调
3、拦截器只能对action请求起作用,而过滤器对所有请求起作用。
4、拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问
5、在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
拦截器可以用来做什么?
1、验证用户登录
2、权限控制
3、日志记录。

6.枚举的使用

参考博客:优先使用注解,慎用枚举
针对类似于 “常量之间存在关联” 的情况使用枚举,毕竟官方是不推荐使用枚举
用法一:常量

/**
 * BigDecimal操作枚举定义
 */
public enum BigDecimalOperations{
    add, subtract, multiply, divide
}
 
public static void main(String[] args) {
	//枚举类型使用
	BigDecimalOperations operations = BigDecimalOperations.add;
	switch (operations) {
		case add:
			//todo
			break;
		case subtract:
			//todo
			break;
		case multiply:
			//todo
			break;
		case divide:
			//todo
			break;
	}
}

7.连接数据库,Jdbc连接

7.1SpringBoot配置
参考:阿里druid-spring-boot-starter 配置

yml文件
spring:
    datasource:
      username: lys
      password: lys
      url: jdbc:mysql://localhost:9909/springboot-mybatis
      driver-class-name: com.mysql.jdbc.Driver

      #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall,log4j
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

7.2 Jdbc获取连接

mysql.properties文件:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:9909/dream?useUnicode=true&characterEncoding=UTF-8
username=root
password=aaa




	private static  String DRIVER_NAME ;
	private static  String URL;
	private static  String USER_NAME;
	private static  String PWD;
	static {
		InputStream inputStream = DBConnection.class.getClassLoader().getResourceAsStream("db.properties");
		Properties properties = new Properties();
		try {
			properties.load(inputStream);
			
			DRIVER_NAME = properties.getProperty("jdbc.driver");
			URL = properties.getProperty("jdbc.url");
			USER_NAME = properties.getProperty("jdbc.user");
			PWD = properties.getProperty("jdbc.password");
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	//获取连接
	public static Connection getConnection() {
		Connection connection = null;
		try {
			Class.forName(DRIVER_NAME);
			connection = DriverManager.getConnection(URL, USER_NAME,PWD);
			
			
		} catch (ClassNotFoundException | SQLException e) {
			e.printStackTrace();
		}
		return connection;
		
	}

7.3 Jdbc-Druid获取连接,需要druid-1.1.6.jar

db-druid.properties文件:
driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
username=scott
password=tiger


public class DBPoolConnection {

	   private static DBPoolConnection dbPoolConnection = null;
	   private static DruidDataSource druidDataSource = null;
	   
	   static {
	       Properties properties = loadPropertiesFile("db-druid.properties");
	       try {
	           druidDataSource = (DruidDataSource)DruidDataSourceFactory.createDataSource(properties);    //DruidDataSrouce工厂模式
	       } catch (Exception e) {
	           e.printStackTrace();
	       }
	   }
	   
	   /**
	    * 数据库连接池单例
	    * @return
	    */
	   public static synchronized DBPoolConnection getInstance(){
	       if (null == dbPoolConnection){
	           dbPoolConnection = new DBPoolConnection();
	       }
	       return dbPoolConnection;
	   }

	   /**
	    * 返回druid数据库连接
	    * @return
	    * @throws SQLException
	    */
	   public DruidPooledConnection getConnection() throws SQLException{
	       return druidDataSource.getConnection();
	   }
	   /**
	    * @param string 配置文件名
	    * @return Properties对象
	    */
	   private static Properties loadPropertiesFile(String fullFile) {
	       if (null == fullFile || fullFile.equals("")){
	           throw new IllegalArgumentException("Properties file path can not be null" + fullFile);
	       }
	       InputStream inputStream = DBPoolConnection.class.getClassLoader().getResourceAsStream(fullFile);
	       Properties p =null;
	       try {
	           p = new Properties();
	           p.load(inputStream);
	       } catch (Exception e) {
	           e.printStackTrace();
	       } finally {
	           try {
	               if (null != inputStream){
	                   inputStream.close();
	               }
	           } catch (Exception e) {
	               e.printStackTrace();
	           }
	       }
	       
	       return p;
	   }
}

8.Jdbc实现分页+模糊查询

Mysql使用limit分页:LIMIT ?,?,计算规则:
参数1 : (当前是第几页-1) * 每页显示的记录数
参数2:每页显示的记录数

	public class PageBean<T> {
	
		private List<T> data;//當前页的数据
		private int currentPage = 1;//当前是第几页
		private int totalPage;//总页数
		private int recordOfPage = 3;//每页显示的记录数
		
		public PageBean(int totalNum) {//totalNum为总记录数
			if(totalNum % recordOfPage == 0 ) {
				this.totalPage = totalNum / recordOfPage;
			}else {
				this.totalPage = (totalNum / recordOfPage ) + 1;
			}
		}
	
		public List<T> getData() {
			return data;
		}
	
		public void setData(List<T> data) {
			this.data = data;
		}
	
		public int getCurrentPage() {
			return currentPage;
		}
	
		public void setCurrentPage(int currentPage) {
			this.currentPage = currentPage;
			
			if(currentPage < 1) {
				this.currentPage = 1;
			}
			if(currentPage > totalPage) {
				this.currentPage = totalPage;
			}
		}
	
		public int getTotalPage() {
			return totalPage;
		}
	
		public int getRecordOfPage() {
			return recordOfPage;
		}
		
	}

	public PageBean<Student> query(PageBean<Student> pageBean,StudentDto studentDto) throws DaoException{
		List<Student> students = new ArrayList<>();
		try {
			StringBuilder sb = new StringBuilder("SELECT * FROM student s LEFT JOIN team t ON s.tid=t.tid where 1=1");  
			if(studentDto.getMinTime() != null) {
				sb.append(" AND entrance>=?");
			}
			if(studentDto.getMaxTime() != null) {
				sb.append(" AND entrance<=?");
			}
			if(StringTool.isNotEmpty(studentDto.getTeamNum())) {
				sb.append(" AND s.tid=?");
			}
			if(StringTool.isNotEmpty(studentDto.getKeyword())) {
				sb.append(" AND name like concat('%', ? ,'%')");
			}
			sb.append(" LIMIT ?,?");
			
			
			connection = DruidTool.getConnection();
			preparedStatement = connection.prepareStatement(sb.toString());
			int index = 1;
			if (studentDto.getMinTime() != null) {
				preparedStatement.setObject(index, studentDto.getMinTime());
				index++;
			}
			if (studentDto.getMaxTime() != null) {
				preparedStatement.setObject(index, studentDto.getMaxTime());
				index++;
			}
			if (studentDto.getTeamNum() != null) {
				preparedStatement.setObject(index, studentDto.getTeamNum());
				index++;
			}
			if (StringTool.isNotEmpty(studentDto.getKeyword())) {
				preparedStatement.setObject(index, studentDto.getKeyword());
				index++;
			}
			
			preparedStatement.setInt(index, (pageBean.getCurrentPage()-1) * pageBean.getRecordOfPage());
			index++;
			preparedStatement.setInt(index, pageBean.getRecordOfPage());
			
			resultSet = preparedStatement.executeQuery();
			
			Student student;
			while (resultSet.next()) {
				int sid = resultSet.getInt("sid");
				String name = resultSet.getString("name");
				String sex = resultSet.getString("sex");
				int age = resultSet.getInt("age");
				String hobby = resultSet.getString("hobby");
				String address = resultSet.getString("address");
				
				Integer tid = resultSet.getInt("s.tid");
				String tname = resultSet.getString("tname");
				Team team = new Team(tid,tname);
				
				String entrance = resultSet.getString("entrance");
				student = new Student(sid, name, sex, age, hobby, address, team, entrance);
				students.add(student);
			}
			
			pageBean.setData(students);
			
		} catch (Exception e) {
			logger.error("[StudentDaoImpl query(PageBean<Student> pageBean,StudentDto studentDto)方法]" + e.getMessage());
			throw new DaoException(e);
		} finally {
			this.close();
		}
		return pageBean;
	}

9.IO流

参考:Java IO流学习总结一:输入输出流

常用的节点流

  • 父 类 :InputStream OutputStreamReaderWriter
  • 文 件 :FileInputStreamFileOutputStreanFileReaderFileWriter 文件进行处理的节点流
  • 数 组 :ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter 对 数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
  • 字符串 :StringReaderStringWriter 对字符串进行处理的节点流
  • 管 道 :PipedInputStreamPipedOutputStreamPipedReader 、PipedWriter 对管道进行处理的节点流

9.1序列化和反序列化

序列化:对象转换为二进制的形式保存起来或者通过网络进行传输。
反序列化:将二进制信息还原成对象

1、将要序列化的对象所在的类实现Serializable接口
2、使用ObjectOutputStream来进行序列化 ,使用ObjectInputStream来进行反序列化
3、serialVersionUID:用于序列化和反序列化的时候使用同一个类
4、transient修饰符,在序列化的时候不会保存用transient修饰的属性
5、static修饰的属性,序列化的时候也不考虑
6、继承关系,父类不实现Serializable,父类上面的属性是不会被序列化的,如果想序列化,那么父类也要实现Serializable

	/**
	 * 读取对象(反序列化)
	 */
	@SuppressWarnings("unchecked")
	public static HashMap<String, PersonInfo> readConfig() {
		String filepath = ConfigUtil.class.getResource("/").getFile();
		String webapps = new File(filepath).getParentFile().getParentFile().getParentFile().getPath();
		ObjectInputStream ois = null;
		try {
			File file = new File(webapps + "/zhxq_source/"+A_ID+"/old.obj");
			if (file.exists()) {
				ois = new ObjectInputStream(new FileInputStream(file));
				Object obj = ois.readObject();
				return (HashMap<String, PersonInfo>)obj;
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			if(ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
		
	}
	
	/**
	 * 写入对象(序列化)
	 * @param map
	 */
	public static void writeConfig(Map<String, PersonInfo> map) {
		String filepath = RefreshPerson.class.getResource("/").getFile();
		String webapps = new File(filepath).getParentFile().getParentFile().getParentFile().getPath();
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream(webapps + "/zhxq_source/"+A_ID+"/old.obj"));
			oos.writeObject(map);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
	}

9.2 ByteArrayOutputStream和ByteArrayInputStream

参考:Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream)

内存操作流是为了临时处理数据而设置的。内存操作流分为ByteArrayOutputStreamByteArrayInputStream
ByteArrayOutputStream 常用于存储数据以用于一次写入。

1、ByteArrayOutputStream类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()toString() 获取数据。关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。

2、ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。

 //ByteArrayOutputStream 常用于存储数据以用于一次写入
 public static String getRest(HttpServletRequest request) throws IOException {
     InputStream inStream = request.getInputStream();
     ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
     byte[] buffer = new byte[1024];
     int len = 0;
     while ((len = inStream.read(buffer)) != -1) {
         outSteam.write(buffer, 0, len);
     }
     String rest = new String(outSteam.toByteArray(), "utf-8");
     outSteam.close();
     inStream.close();
     return rest;
 }
 //ByteArrayOutputStream 常用于存储数据以用于一次写入
 private String getInputSteam(BufferedImage image) throws IOException {
      ByteArrayOutputStream os = new ByteArrayOutputStream();
      ImageIO.write(image, "png", os);
      InputStream input = new ByteArrayInputStream(os.toByteArray());
      return Base64.encode(input);
  }

public static void uploadPerImg(String token, String personId, String imgpath) throws Exception {
	//上传人脸图片,方式2 从文件流上传
	ByteArrayOutputStream btos = new ByteArrayOutputStream();
	HttpUtils.downloadFile("http://localhost:81/"+imgpath, btos );
	//小程序的图片
	ByteArrayInputStream oldImgIn = new ByteArrayInputStream(btos.toByteArray());
	//压缩图片后的文件流
	ByteArrayOutputStream newImgOut = new ByteArrayOutputStream();
	//压缩,通过写入到ByteArrayOutputStream的byte 数组中,缓存了数据
	ImgUtils.scaleFromStream(oldImgIn , newImgOut , 260, 300, false);
	
	String url = HTTP+"/CardSolution/common/saveImageToByte?personId="+personId+"&token="+token;
	//把输出流转成InputStream输入流,传入byte[]二进制数据创建ByteArrayInputStream,
	String rs = HttpUtils.uploadFile(url, "file", null, new ByteArrayInputStream(newImgOut.toByteArray())  ,null);
	SysLog.print(rs);
	
}

二,代码优化必须注意的细节

参考博客:Java代码优化必须注意的细节

1.乘法和除法使用位运算

//在计算机底层,对位运算是最方便、最快的
//因此如果使用算术运算符 + - * / %时,如果算术运算是2的n次方可以使用位运算 
for (int val = 0; val < 100; val += 10) {
	//等同于 val * 8(2的3次方)
    int a = val << 3;
    //等同于 val / 2(2的1次方)
    int b = val >> 1;
    System.out.println(a);
}

2.能确定数组大小尽量使用Array,无法确定才使用ArrayList

分析:数组是JAVA语言内置的数据类型,可以快速的访问其他的元素,缺点不可变。
ArrayList:底层数据结构基于数组实现,当数组长度超出会以原50%长度延长,优点可变

使用:
数组使用toString()打印结果是:[I@18a992f
集合使用toString()可以打印出内部内容,因为集合父类AbstractCollections重写了Object的toString()方法。

//一维数组
String[] array = { "1", "2", "3", "4" };
//二维数组
String[][] array2 = new String[2][];

//java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
//给数组赋值:通过 fill 方法。
//对数组排序:通过 sort 方法,按升序。
//比较数组:通过 equals 方法比较数组中元素值是否相等。
//查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。
Arrays.fill(array, "6");

//System中提供了一个native静态方法arraycopy(),拷贝数组效率高
//ArrayList模拟add方法
public void add(Object object) {
	if(size>=element.length) {
		Object[] newArr = new Object[element.length+(element.length>>1)];
		//原数组,原数组下标开始,目标数组,目标数组下标开始,拷贝个数
		System.arraycopy(element, 0, newArr, 0, element.length);
		element = newArr;
	}
	element[size] = object;
	size++;
}	

3.Integer数据类型比较不能用==而要用equals方法

”==”比较对象的内存地址

Integer a = 50;  
Integer b = 50;  
System.out.println(a == b);//true  
System.out.println(a.equals(b));//true  
a = 500;  
b = 500;  
System.out.println(a == b);//false
System.out.println(a.equals(b));//true 
//原因:Integer类型在比较大小的时候调用了Integer.valueOf()方法,
//值在[-128,127]范围内是直接用的int原始数据类型,而超出了这个范围则是new一个Integer新对象
      
//比较int则不受影响
int aaa = 500;
int bbb = 500;
System.out.println(aaa == bbb);//true

4.基本数据类型转为字符串,.toString()是最快的方式

1、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断
2、Integer.toString()方法直接调用
3、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串
三者对比下来,明显是toString()最快、String.valueOf()次之、i + “”最慢。

5.对资源的close()建议分开操作

public static void close(PreparedStatement prepared, Connection connection) {
	//不要使用此方式释放资源,如果prepared抛出异常后面则得不到执行
	try {
		prepared.close();
		connection.close();
	} catch (SQLException e) {
		e.printStackTrace();
	}
	//建议修改为
	if (prepared != null) {
		try {
			prepared.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	if (connection != null) {
		try {
			connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

6.ThreadLocal使用前或者使用后一定要先remove

基本上现在项目都使用了线程池技术,可以动态配置线程数、可以重用线程。
并且同时还使用ThreadLocal,线程不销毁意味着上条线程set的ThreadLocal.ThreadLocalMap中的数据依然存在

7.对于float和double数据尽量使用BigDecimal类运算

使用float、double类型数据进行计算会出现精度问题,需要使用BigDecimal类运算。

public class BigDecimalUtil {
	public static void main(String[] args) throws Exception {
//		BigDecimal加减乘时都没有出现问题,但是到除法运算如10/3除不尽时,会报错:没有可精确表示的十进制结果。
//		可以使用BigDecimal.setScale()方法用于格式化小数点
//		setScale(1)表示保留一位小数,默认用四舍五入方式 
//		setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3 
//		setScale(1,BigDecimal.ROUND_UP)进位处理,2.35变成2.4 
//		setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4
//		setScale(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35变成2.3,如果是5则向下舍
		//除法运算
		System.out.println(BigDecimalUtil.operationASMD(10, 3, BigDecimalOperations.divide, 2, BigDecimal.ROUND_HALF_UP));
	}
	
    /**
     * BigDecimal操作枚举定义
     */
    public enum BigDecimalOperations{
        add, subtract, multiply, divide
    }
    /**
     * Bigdecimal加减乘除运算
     * @param numOne [String Integer Long Double Bigdecimal] 数值1
     * @param numTwo [String Integer Long Double Bigdecimal] 数值2
     * @param bigDecimalOpration 操作
     * @param scale 保留几位小数
     * @param roundingMode 格式化小数方式
     * @return
     * @throws Exception
     */
    public static BigDecimal operationASMD(Object numOne,Object numTwo,BigDecimalOperations bigDecimalOpration,int scale,int roundingMode) throws Exception{
        BigDecimal num1 = new BigDecimal(String.valueOf(numOne)).setScale(scale,roundingMode);
        BigDecimal num2 = new BigDecimal(String.valueOf(numTwo)).setScale(scale,roundingMode);
        switch (bigDecimalOpration){
            case add: return num1.add(num2).setScale(scale,roundingMode);
            case subtract: return num1.subtract(num2).setScale(scale,roundingMode);
            case multiply: return num1.multiply(num2).setScale(scale,roundingMode);
            case divide: return num1.divide(num2, scale, roundingMode);
        }
        return null;
    }
}

三,JAVA封装工具类

参考:java常用的工具类
参考:整理收集的一些常用java工具类

/**
 * Date自定义格式yyyy-MM-dd HH:mm:ss
 * @param date
 * @param layout
 * @return
 */
public static String dateFormat(Date date, String layout){
	if(date == null) return null;
	return new SimpleDateFormat(layout).format(date);
}
/**
 * 字符串转Date
 * @param date 标准格式yyyy-MM-dd HH:mm:ss
 * @return
 */
public static Date parseDate(String date){
	try {
		return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date);
	} catch (ParseException e) {
		return null;
	}
}

四,加密算法AES和RSA

1.AES

对称加密有:DES,AES,3DES
AES:对称加解密算法中,最为安全加密算法。

参考:java使用AES加密解密 AES-128-ECB加密

示例:

import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AESTest {
	/**
	 * AES加密
	 * @param sSrc 加密数据
	 * @param sKey 此处使用AES-128-ECB加密模式,key需要为16位
	 * @return
	 * @throws Exception
	 */
	public static String Encrypt(String sSrc, String sKey) throws Exception {
		if (sKey == null) {
			System.out.print("Key为空null");
			return null;
		}
		// 判断Key是否为16位
		if (sKey.length() != 16) {
			System.out.print("Key长度不是16位");
			return null;
		}
		byte[] raw = sKey.getBytes("utf-8");
		SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
		Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// "算法/模式/补码方式"
		cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
		byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));

		return new Base64().encodeToString(encrypted);// 此处使用BASE64做转码功能,同时能起到2次加密的作用。
	}

	/**
	 * 解密
	 * @param sSrc 加密数据
	 * @param sKey 此处使用AES-128-ECB加密模式,key需要为16位
	 * @return
	 * @throws Exception
	 */
	public static String Decrypt(String sSrc, String sKey) throws Exception {
		try {
			// 判断Key是否正确
			if (sKey == null) {
				System.out.print("Key为空null");
				return null;
			}
			// 判断Key是否为16位
			if (sKey.length() != 16) {
				System.out.print("Key长度不是16位");
				return null;
			}
			byte[] raw = sKey.getBytes("utf-8");
			SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			cipher.init(Cipher.DECRYPT_MODE, skeySpec);
			byte[] encrypted1 = new Base64().decode(sSrc);// 先用base64解密
			try {
				byte[] original = cipher.doFinal(encrypted1);
				String originalString = new String(original, "utf-8");
				return originalString;
			} catch (Exception e) {
				System.out.println(e.toString());
				return null;
			}
		} catch (Exception ex) {
			System.out.println(ex.toString());
			return null;
		}
	}
	public static void main(String[] args) throws Exception{
		//密码:用于加密解密
//		String password = "1234567890123456";
		String password = "$$1234567890123.";
		//加密
		String resStr = AESTest.Encrypt("Test content:这是测试内容!", password);
        System.out.println("加密后的字串是:"+resStr);
        //解密
        System.out.println("解密后的字串是:"+AESTest.Decrypt(resStr, password));
    }
}

2.RSA

RSA是一种非对称加密算法,使用RSA一般需要产生公钥和私钥,当采用公钥加密时,使用私钥解密;采用私钥加密时,使用公钥解密。

参考:RSA加密与解密(Java实现)

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;

public class RSATest {
	private static Map<Integer, String> keyMap = new HashMap<Integer, String>();  //用于封装随机产生的公钥与私钥
	
	 /**
     * RSA公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    public static String encrypt(String str, String publicKey) throws Exception {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);

        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
        return outStr;
    }
    /** 
	 * RSA私钥解密
	 *  
	 * @param str 
	 *            加密字符串
	 * @param privateKey 
	 *            私钥 
	 * @return 铭文
	 * @throws Exception 
	 *             解密过程中的异常信息 
	 */  
	public static String decrypt(String str, String privateKey) throws Exception{
		//64位解码加密后的字符串
		byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
		//base64编码的私钥
		byte[] decoded = Base64.decodeBase64(privateKey);  
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));  
		//RSA解密
		Cipher cipher = Cipher.getInstance("RSA");
		cipher.init(Cipher.DECRYPT_MODE, priKey);
		String outStr = new String(cipher.doFinal(inputByte));
		return outStr;
	}
	/** 
	 * 随机生成密钥对 
	 * @throws NoSuchAlgorithmException 
	 */  
	public static void genKeyPair() throws NoSuchAlgorithmException {  
		// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象  
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");  
		// 初始化密钥对生成器,密钥大小为96-1024位  
		keyPairGen.initialize(1024,new SecureRandom());  
		// 生成一个密钥对,保存在keyPair中  
		KeyPair keyPair = keyPairGen.generateKeyPair();  
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥  
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥  
		String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));  
		// 得到私钥字符串  
		String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));  
		// 将公钥和私钥保存到Map
		keyMap.put(0,publicKeyString);  //0表示公钥
		keyMap.put(1,privateKeyString);  //1表示私钥
	}  
	
	 public static void main(String[] args) throws Exception{
		//生成公钥和私钥
		genKeyPair();
		//加密字符串
		String message = "Test content:这是测试内容!";
		System.out.println("随机生成的公钥为:" + keyMap.get(0));
		System.out.println("随机生成的私钥为:" + keyMap.get(1));
		String messageEn = encrypt(message, keyMap.get(0));
		System.out.println(message + "\t加密后的字符串为:" + messageEn);
		String messageDe = decrypt(messageEn, keyMap.get(1));
		System.out.println("还原后的字符串为:" + messageDe);
     }
}

3.MD5

MD5特点:
1.md5是不可逆算法
2.会被破解,解决方式:可以多次MD5加密,也可在加密时加盐值
作用:md5可用于数字签名,及文件完整性验证以及口令加密等方面

参考:JAVA中获取文件MD5值的四种方法
md5加密的几种方法

1.原生的加密方法

参考:java之使用md5对密码进行加密

import java.security.MessageDigest;
public class TestMD5 {
    //盐,用于混交md5
    private static final String SALT = "&%1A2Asc*&%$$#@";
    /**
     * 原生的加密方法
     *
     * @param value 需要加密的字符串
     * @return 加密后的32位字符串
     */
    public static String md5(String value) {
        try {
            value = value + SALT;
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(value.getBytes("UTF8"));
            byte message[] = messageDigest.digest();
            String result = "";
            for (int i = 0; i < message.length; i++) {
                result += Integer.toHexString((0x000000FF & message[i]) | 0xFFFFFF00).substring(6);
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public static void main(String[] args) {
        /**
         * 需要加密的密码尽可能的要复杂,包含大小写字母和数字,
         * 长度不小于8为,加密必须加盐,如果需要更高要求,可以多次加密,多次加盐
         */
        System.out.println(md5("12ASD23klk935"));
        //2596d9bb596d17aa85b4f2655a5e430f
    }
}

2.使用到shiro权限框架

import org.apache.shiro.crypto.hash.SimpleHash;
public static void main(String[] args) {
    String hashAlgorithmName = "MD5";//加密方式
    Object crdentials = "520";//密码原值
    Object salt = "&%1A2Asc*&%$$#@";//盐值
    int hashIterations = 1024;//加密1024次
    Object result = new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations);
    System.out.println(result);
}

3.使用到security权限框架

参考:使用Spring Security下的BCryptPasswordEncoder进行密码加密
BCryptPasswordEncoder的加密是带salt的加密,它的salt也是嵌在加密后的密文中的,所以不用保存salt。
encode用来加密,一个matches用来解密

	@Autowired
	private BCryptPasswordEncoder passwordEncoder;
	
	public void changePassword(String username, String oldPassword, String newPassword) {
		SysUser u = userDao.getUser(username);
		if (u == null) {
			throw new IllegalArgumentException("用户不存在");
		}
		//shi
		if (!passwordEncoder.matches(oldPassword, u.getPassword())) {
			throw new IllegalArgumentException("旧密码错误");
		}

		userDao.changePassword(u.getId(), passwordEncoder.encode(newPassword));

		log.debug("修改{}的密码", username);
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值