1.导包
ctrl+shift+字母o ,回车
2.保留指定位数的小数
(1)保留几位小数 “%.nf” ,n保留的小数位数 ,保留2位小数 "%.2f"
(2)//借助 java.text.DecimalFormat类 ,格式化数字 保留几位小数,就留几个“0”
DecimalFormat df = new DecimalFormat(".000");
//借助df格式化pi str="3.142" ,是一个字符串
//format()方法,将 double 按指定格式 输出成字符串
String str = df.format(pi);
System.out.println("str="+str);
// 将字符串 转换成 double类型的浮点数
double result = Double.parseDouble(str);
3.Math.round(a)
4.多重if
范围打乱,顺序对结果又影响 ,要么从大往小写,要么从小往大写,对于顺序打乱,解决办法:加上逻辑限制条件。
5.随机数
[min, max) 整数 (int)(Math.random()*(max-min)+min);
Random ra = new Random();
[min, max) 整数: ra.nextInt(max-min)+min
6.冒泡排序
int[] arr = {5,4,3,2,1};
//声明中间变量,用于交换
int temp=0;
//控制轮数
for(int k=0;k System.out.println("\n\n第"+(k+1)+"轮开始时:"+Arrays.toString(arr)); //内层控制的每一轮比较的次数,要保证比较次数在减少, 取值,4,3,2,1 for(int i=0;i //如果前一个比后一个大需要交换 if(arr[i]>arr[i+1]){ temp=arr[i]; arr[i]= arr[i+1]; arr[i+1]=temp; } System.out.println("第"+(k+1)+"轮第"+(i+1)+"次比较:"+Arrays.toString(arr)); } System.out.println("第"+(k+1)+"轮结束后:"+Arrays.toString(arr)+"\n\n"); } 选择排序 public static void main(String[] args) { int [] arr=new int [] {45,65,32,33,12,1}; for(int i=0;i int index=i; for(int j=i+1;j if(arr[j] int temp=arr[index]; arr[index]=arr[j]; arr[j]=temp; } } } System.out.println(Arrays.toString(arr)); } 快速排序 public static void main(String[] args) { int[] arr = new int[] { 1, 2, 4, 5, 7, 4, 5, 3, 9, 0 }; //System.out.println(Arrays.toString(arr)); quickSort(arr); System.out.println(Arrays.toString(arr)); } private static void quickSort(int[] arr) { if (arr.length > 0) { quickSort(arr, 0, arr.length - 1); } } private static void quickSort(int[] arr, int low, int high) { // 1.递归算法出口 if (low > high) { // 放在key之前,防止下标越界 return; } // 2. 存 int i = low; int j = high; // key int key = arr[i]; // 3.完成一趟排序 while (i < j) { // 从右往左找到第一个小于key的数 while (i < j && arr[j] > key) { j--; } // 从左往右找第一个大于key的数 while (i < j && arr[i] <= key) { i++; } // 交换 if (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 当i==j时,调整key的位置 int p = arr[i]; arr[i] = arr[low]; arr[low] = p; // 对key左边的数快排 quickSort(arr, low, i - 1); // 对key右边的数快排 quickSort(arr, i + 1, high); } http://developer.51cto.com/art/201403/430986.htm 7.数组拷贝 (1) int[] arr = {10,12,13,14,15}; int[] brr = arr.clone(); //clone() (2)System.arraycopy(要拷贝的原始数组arr,原始数组中元素的起始下标从0开始srcfrom,目标数组brr,目标数组中起始位置destfrom,要拷贝的元素个数num) * 需要满足的条件: * srcfrom>=0 * destfrom>=0 * srcfrom +num <= arr.length * destfrom+num<=brr.length (3)copyOf(int[] original, int newLength) * copyOf(要拷贝的原始数组,要拷贝的元素个数num) (4)copyOfRange(要拷贝的原始数组,拷贝的起始索引从0开始from ,结束索引to) * 拷贝的索引范围:[from, to) 左闭右开 ,拷贝的元素个数 to-from 8 . 字符串转数组 (1)使用Java split() 方法 split() 方法根据匹配给定的正则表达式来拆分字符串。 注意: . 、 | 和 * 等转义字符,必须得加 \\。多个分隔符,可以用 | 作为连字符。 String str = "0,1,2,3,4,5"; String[] arr = str.split(","); // 用,分割 System.out.println(Arrays.toString(arr)); ` int a=Integer.parseInt(arr[]) String类型转int型` 9。数组转字符串 String str2 = ArrayUtils.toString(arr, ","); // 数组转字符串(逗号分隔,首尾加大括号) String str4 = StringUtils.join(arr, ","); //StringUtils的join方法 10.charAt(2) 取出指定位置的字符 lastIndexOf()最后一个索引的位置 endWith() /startsWith("aa")判断是否以aa开头,返回布尔值 trim() 去掉两边空格 11。 int---》String Int型转字符串型 String s1 = String.valueOf(num); String---》 int 字符串型转int型 int num2 = Integer.parseInt(s2); double num3 =Double.parseDouble(s2); 12 . 截取字符串 //取出10 subString(from,to) [from,to) 左闭右开 String num1Str = s.substring(s.indexOf("从")+1, s.indexOf("数到")); String num1Str = s.substring(s.indexOf("从")+1, s.indexOf("数到")); 13.//静态变量 ,可以类名.属性名 ,也可以对象名.属性名 //非static变量/普通变量 ,只能通过对象名.属性名来访问 //静态方法ceshi4(),只能调用静态方法ceshi1(),不能调用普通方法ceshi2(); 14.//1个类:静态成员变量,实例成员变量(普通的成员变量),静态代码块,普通代码块 ,构造 //执行顺序: 静态成员----》静态代码块 ----》实例成员----》普通代码块----》构造 / //1.父类的静态成员 // 父类的静态代码块 // 2.子类的静态成员 // 子类的静态代码块 // 3.父类的实例成员 // 父类的普通代码块(非static代码块) //4.父类的构造Father() // 5.子类的实例成员 // 子类的普通代码块(非static代码块) // 6.子类的构造Son() 15.this() :表示调用本类的无参构造 * this(属性名):表示调用本类的带参构造 ,this(属性1,属性2....) 可以构造的调用,需要放到第1句 16.基本数据类型---》包装类 //直接装箱 int类型的变量,直接赋值给Integer类型的变量 num2 Integer num2 = num1; //int--->Integer int num1=10; Integer num2 = new Integer(num1); Integer num7 = Integer.valueOf(num1); //String--->Integer String s="10"; Integer num3 = new Integer(s); Integer num8 = Integer.valueOf(s); 17.包装类----》基本数据类型 //直接拆箱 Integer类型变量num2,直接赋值给int类型的变量num3 int num3 = num2; //Integer---》int Integer num1 = new Integer(10); int num2 = num1.intValue(); //String--->int String s="123"; int num5=Integer.parseInt(s); **封装**(降低耦合 将类的信息隐藏在类的内部,对外提供公有的方法,实现对该成员属性的存取操作 封装的好处:隐藏类的实现细节,让使用者使用提供的方法来访问数据,可以方便的加入存取操作,限制不合理的操作 **继承** 一个类可以由其他类派生,子类继承父类特征和方法。 只支持单继承 子类可以继承父类Public和protected修饰的属性和方法 在同一个包中可以继承除private以外的所有修饰的属性和方法 子类无法继承父类的构造方法 子类不能抛出 比父类更多的异常 父类的静态方法不能被子类覆盖为非静态方法,同样父类的非静态方法不能被子类覆盖为静态方法 **继承下构造方法的执行过程** 注意:加载顺序:启动类(java虚拟机启动时,被标明为启动类的类)的static block 最先加载(父类静态成员,静态代码块----子类静态成员,静态代码块---父类实例成员,代码块-----父类构造函数------子类实例成员,代码块---子类构造函数) *重写* 方法名相同 参数列表相同 返回值类型相同或是其子类 不能缩小被重写方法的访问权限 **final关键字** final 修饰成员变量,则成为实例常量 final修饰类,类不能被继承 final修饰成员方法,则该方法不能被子类重写。 **super关键字** 在子类构造方法中调用且必须为第一句(调用父类的带参构造方法)super(属性1,属性2..) 使用super关键字 直接调用父类的方法 **多态** 同一引用类型,使用不同的实例而执行不同的操作。 举例说明多态:动物类父类 有“叫”的这样一个动作, 继承它的都是普通类猫 狗等 每个子类叫的方法实现都不一样 ,现在要实现各种动物的叫声 ,如何动态实现 写一个方法把父类做形参传进去 主人类始终要修改,只要新增宠物子类,主人类就需要添加具体动物看病的方式,子类写不尽的,Host类始终需要修改,不合理 * 解决办法:多态来来解决 * 1.创建Cat子类,extends Pet父类,Cat类中是Cat特有的属性 * 2. Host类,不需要修改的,只提供一个空方法,带宠物看病 * public void cure(Pet pet){ * pet.toHospital(); * } * 3.父类Pet中,提供1个空方法 ,public void toHospital(){} * 4.每个子类,看病的方式不同,就把各自子类看病的方式,放到各自的子类中去完成 * 每个子类,去重写父类的toHospital()方法 * * 5.调用的时候,父类引用,指向子类对象,执行的是各自不同的子类对应的操作 **abstract抽象** 抽象方法(只有方法声明,没有方法实现)*abstract void fun();* 抽象类 :包含抽象方法的类是抽象类; 抽象类不能实例化,可以实例化子类来实现父类的方法 * 子类必须重写所有抽象方法才能实例化,否则子类还是一个抽象类* 抽象类有构造方法,可以被本类其他构造方法调用,如果不是private修饰,可以被其子类中的构造方法调用。 abstract修饰类和方法,不能修饰属性和构造方法 **接口** 接口中不能定义变量 可以定义常量 自动用public static final修饰 全局静态常量 接口中所有方法都是抽象方法 自动public abstract 修饰 接口不能实例化 不能有构造方法 接口的实现类必须要实现接口的全部方法,除非这个类是抽象类 一个接口不能实现另一个接口,但可以继承多个其他接口 **Date类** Date date=new Date(); System.out.println("date="+date); 3个子类构造 //java.sql.Date ,默认格式“yyyy-MM-dd" Date date = new Date(System.currentTimeMillis()); // date=2019-03-25 //java.sql.Time ,默认格式:“HH:mm:ss” Time date2 = new Time(System.currentTimeMillis()); //date2=11:53:09 //java.sql.Timestamp 默认格式“ yyyy-MM-dd HH:mm:ss.SSS" 精确到毫秒 //1s = 1000ms ,毫秒的范围[000,999] Timestamp date3 = new Timestamp(System.currentTimeMillis()); //date3=2019-03-25 11:54:20.097 DateFormat:日期格式化类。抽象类无法使用 SimpleDateFormat是其子类可以使用 (1 格式化java.util.Date对象 ,让其按指定的格式来显式 将Date对象 ----format()方法-----》字符串String 来显式 (2 public static String getStrFromDate(Date date,String pattern){ DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = new Date(); String result = df.format(date); System.out.println("date="+date+",格式化后="+DateUtil.getStrFromDate(date, "yyyy-MM-dd")); **Calendar类** Calendar:抽象类,不能直接实例化 ,可以使用其子类 GregorianCalendar Calendar cal = new GregorianCalendar(); 获取时间 Calendar cal = Calendar.getInstance(); 设置时间 Calendar cal = Calendar.getInstance(); // 如果想设置为某个日期,可以一次设置年月日时分秒,由于月份下标从0开始赋值月份要-1 // cal.set(year, month, date, hourOfDay, minute, second); cal.set(2018, 1, 15, 23, 59, 59); **异常** * error与exceptionde 区别* error表示不可处理的异常 通常为内存溢出 jvm崩溃等。 exception表示需要捕捉或者处理的异常 * throw和throws的区别* throw在程序中抛出异常,出现在方法体内,如果执行则一定抛出某种异常对象,且只能抛出一个。 throws表示抛出异常的声明,出现在方法头,声明抛出异常的一种可能性,throws后面可以跟多个异常类。 * try catch finally* try块必须 catch finally必须出现一个 finally一定会执行,除非System.exit(n) try块中有return语句,finally语句也会执行,执行return语句会记下返回值,待finally执行结束后,再向调用者返回其值。 *常见的5种RunTimeException* NullPointerException,空指针异常。 NumberFormatException,数据格式转换错误。 ClassCastException,强制类型转换异常。 IndexOutOfBoundsException,越界异常。 ArithmeticException 算术异常。 除RuntimeException及其子类其他所有的异常都是检查型异常(可查异常) 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。 使用自定义异常的步骤: 1.定义异常类(继承 Throwable 类,Exception类 RuntimeException)。 2.编写构造方法,继承父类的实现。 3.实例化自定义异常对象。 4.使用throw 抛出。 **String、StringBuffer与StringBuilder之间的区别** 1.String含义为引用数据类型,是字符串常量.是不可变的对象,(显然线程安全)在每次对string类型进行改变的时候其实都等同与生成了一个新的String对象.然后指针指向新的String对象,所以经常改变内容的字符串最好不使用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了之后.JVM的垃圾回收(GC)就会开始工作,对系统的性能会产生影响 2.StringBuffer 线程安全的可变字符序列:对StringBuffer对象本身进行操作,而不是生成新的对象.所所以在改变对象引用条件下,一般推荐使用StringBuffer.同时主要是使用append和insert方法, 3.StringBuilder 线程不安全的可变字符序列.提供一个与StringBuffer兼容的API,但不同步.设计作为StringBuffer的一个简易替换,用在字符缓冲区被单个线程使用的时候.效率比StringBuffer更快 区别: a.执行速度:StringBuilder > StringBuffer > String b.线程安全:StringBuffer线程安全.StringBuilder线程不安全 c.String适用与少量字符串操作 StringBuilder适用单线程下在字符缓冲区下进行大量操作的情况 StringBuffer使用多线程下在字符缓冲区进行大量操作的情况 **集合** Collection 接口存储一组不唯一,无序的对象。 List接口存储一组不唯一,有序,可重复的对象。ArrayList、LinkedList和Vector(淘汰)是主要的实现类 Set接口存储唯一,无序的对象。HashSet和TreeSet是主要的实现类。 Map接口存储一组键—值对象,提供Key—Value的映射。其中key列就是一个集合,key不能重复,但是value可以重复。 HashMap、TreeMap和Hashtable是Map的主要实现类。 **Vector扩容机制** vector 默认的扩容机制是按照容器现有容量的一倍进行增长。由于 Vector 容器分配的是一块连续的内存空间, 每次容器的增长并不是在原有连续的内容空间后进行简单的叠加, 而是重新申请一块更大的新内存, 并把现有容器中的元素逐个复制过去, 然后销毁原有内存。 举例:vector 初始化时申请的空间大小为 6 , 存入了 6 个元素, 当向 vector 中插入第 7 个元素“ 6” 时, vector 会利用自己的扩容机制重新申请空间, 数据存放结构如图 1 所示(_First 指向使用空间的头部,_Last 指向使用空间大小(size)的尾部,_End 指向使用空间容量(capacity)的尾部)。 ![](https://i.imgur.com/oHemGds.png) **ArrayList底层实现原理** ArrayList的底层数据结构就是一个数组,数组元素的类型为Object类型,对ArrayList的所有操作底层都是基于数组的 ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。 ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素。 底层使用数组实现 该集合是可变长度数组,数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量增长大约是其容量的1.5倍,这种操作的代价很高。 采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险 remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC (补充) **LinkedList底层实现原理** LinkedList是List接口的双向链表非同步实现,并允许包括null在内的所有元素。 底层的数据结构是基于双向链表的,该数据结构我们称为节点 双向链表节点对应的类Node的实例,Node中包含成员变量:prev,next,item。其中,prev是该节点的上一个节点,next是该节点的下一个节点,item是该节点所包含的值。 它的查找是分两半查找,先判断index是在链表的哪一半,然后再去对应区域查找,这样最多只要遍历链表的一半节点即可找到 **HashMap底层实现原理(jdk1.8)** 从结构上讲,hashmap是位桶(Node数组)+链表+红黑树实现的,链表是为了解决hash冲突的,当链表长度超过阈值(8)时,将链表转换为红黑树,大大减少查找时间。 主干是Node数组,包含一个键值对,实现了Map.entry接口,它的初始容量为16, 当链表数组的容量超过初始容量的0.75时,再散列将链表数组扩大2倍,把原链表数组的搬移到新的数组中 *如何getValue* get方法时获取key的Hash值,通过计算hash&(n-1)得到在链表数组中的位置,判断计算的key与参数key是否相等,不等集遍历后面的链表找到相同的key值返回对应的Value值即可。 *如何put 判断键值对数组tab[]是否为空或为null,否则以默认大小resize(); 根据键值key计算hash值得到插入的数组索引i,如果tab[i]==null,直接新建节点添加 判断当前数组中处理hash冲突的方式为链表还是红黑树(check第一个节点类型即可),分别处理 *HasMap的扩容机制resize();* 构造hash表时,如果不指明初始大小,默认大小为16(即Node数组大小16),如果Node[]数组中的元素达到(填充比*Node.length)重新调整HashMap大小 变为原来2倍大小,扩容很耗时 **Hashtable实现原理** 不允许出现null值null键,线程同步,et/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化。 线程安全。 **ConcurrentHashMap实现原理** ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现。 ConcurrentHashMap采用了非常精妙的"分段锁"策略,ConcurrentHashMap的主干是个Segment数组。 final Segment ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示 tatic final class HashEntry final K key; final int hash; volatile V value; final HashEntry } 可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。 **IO操作** * 1.删除文件及其下面的子目录,子文件,不包括最外层的父文件夹 public static void delAllFileExceptOuter(File dir){ if(dir.exists() && dir.isDirectory()){ //获取子文件列表 File[] arr = dir.listFiles(); //遍历 for(File f:arr){ //递归调用自身方法,继续内层的删除 delAllFileExceptOuter(f); f.delete(); } } } * 2.删除文件及其下面的子目录,子文件,包括最外层的父文件夹 * public static void delAllFileIncludeOuter(File dir){ if(dir.exists() && dir.isDirectory()){ //获取子文件列表 File[] arr = dir.listFiles(); //遍历 for(File f:arr){ //递归调用自身方法,继续内层的删除 delAllFileIncludeOuter(f); f.delete(); } dir.delete(); } } * 3.拷贝所有的子文件夹,不拷贝子文件 public static void copyAllDir(File src,File dest){ if(src.isDirectory()){ if(!dest.exists()){ dest.mkdirs(); } //获取src的子文件列表 File[] arr = src.listFiles(); //遍历 for(File f:arr){ //构建新的子文件对象 subSrc=new File("E:/others/", c); // subDest = new File("D:/others", c); File subSrc = new File(src,f.getName()); File subDest = new File(dest,f.getName()); System.out.println("###subSrc="+subSrc+",subDest="+subDest); //递归调用自身方法 copyAllDir(subSrc, subDest); } } } * 4.拷贝单个子文件 public static void copySingleFile(File src,File dest){ if(src.isDirectory()){ System.out.println("只能拷贝文件!"); return; } // if(dest.isDirectory()){ System.out.println("要拷贝的文件和父目录中的文件夹重名,不能拷贝!"); return; } //声明输入流对象,输出流对象 FileInputStream fis= null; FileOutputStream fos = null; try { //建立输入流和源文件之间的联系 fis = new FileInputStream(src); //建立输出流和目标文件之间的联系 fos = new FileOutputStream(dest); //声明byte[]数组,用来存储读取的内容 ,数组的容量 n:整数值,任意定,项目中一般用1024 byte[] buffer = new byte[3]; //声明int变量,用来存储read()方法的返回值,实际上存储就是实际读取到的字节个数 int len=fis.read(buffer); while(len!=-1){ //读取的内容报错在byte数组中 ,需要转换成String,才能打印输出 // String data = new String(buffer,0,len); // System.out.println("len="+len+"data="+data); //读多少,写多少出去 fos.write(buffer, 0, len); //刷新 fos.flush(); //接着读 len=fis.read(buffer); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ //释放资源 、关闭流 //先打开的后关闭,先创建的后关闭 if(null!=fos){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if(null!=fis){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } * 5.拷贝所有的子文件,子目录 public static void copyAllFiles(File src,File dest){ if(src.getParent()==null && dest.getParent()==null){ //证明相对路径下,同级之间的拷贝,允许通过 }else if((src.getParent()==null && dest.getParent()!=null) && (dest.getAbsolutePath().contains(src.getAbsolutePath()))){ System.out.println("***父目录不能拷贝到子目录中!"); return; }else if(dest.getAbsolutePath().contains(src.getAbsolutePath()) && !src.getParent().equals(dest.getParent())){ //src的parent()和dest的parent()不同 System.out.println("父目录不能拷贝到子目录中!"); return; } if(src.isDirectory()){//如果是目录,拷贝对应的目录 if(!dest.exists()){ dest.mkdirs(); } //获取src的子文件列表 File[] arr = src.listFiles(); //遍历 for(File f:arr){ //构建新的子文件对象 File subDest = new File(dest,f.getName()); //递归调用自身方法 copyAllFiles(f, subDest); } }else if(src.isFile()){ //如果是文件,拷贝文件 copySingleFile(src, dest); } } **IO流** * 面试题: 字符串和字节数组,字符串和字符数组之间的相互转换? * String s---->byte[] buffer * String s="asd"; * byte[] buffer=new byte[1024]; * buffer = s.getBytes(); * * byte[] buffer ---->String s *s = new String(buffer); *s = new String(buffer,int from, int len); *from:buffer数组的起始索引 *len:要转换的字节数 (有几个字节) * * * String s ---->char[] buffer *buffer = s.toCharArray(); * * char[] buffer----->String s * s = new String(buffer); * * s= new String(buffer,int from,int len); * from:buffer字符数组的起始索引 * len:要转换的字符个数 (与几个字符) **IO流文件流FileInputStream/FileOutstream** 声明输入输入流输出流对象 FileInputStream fis=null; FileOutputStream fos=null; 建立输入流,输出流与源文件的联系 fis=new FileInputStream(" "); fos=new FileOutputStream(" "); 声明byte[]字节数组,用于存储读取的内容 byte[] buffer=new byte[1024]; int len=0; while((len=fis.read(buffer))!=-1){ String data=new String(buffer,0,len); syso(data); len=fis.read(buffer,0,buffer.length); fos.write(buffer,0,len); fos.flush(); } 拷贝 //声明输入流对象,输出流对象 FileReader fr = null; FileWriter fw = null; try { //建立输入流和源文件之间的联系 fr = new FileReader("E:/others/ceshi.txt"); //建立输出流和目标文件之间的联系 fw = new FileWriter("D:/test/ceshi.txt"); char[] buffer = new char[4]; //len实际读取的字符数 int len=0; while((len=fr.read(buffer))!=-1){ // fw.write(buffer, 0, len); //拷贝的时候,一定要带上偏移量 String s = new String(buffer,0,len); fw.write(s); fw.flush(); } **缓冲流BufferInputStream/BufferOuputStream/BUfferReader/bufferWriter** //声明输入流对象,输出流对象 BufferedReader br = null; BufferedWriter bw = null; try { //建立输入流和源文件之间的联系 br = new BufferedReader(new FileReader("E:/others/ceshi.txt")); //建立输出流和目标文件之间的联系 bw = new BufferedWriter(new FileWriter("D:/test/ceshi.txt")); //声明String变量,用于存储读取的内容 String data = null; while((data=br.readLine())!=null){ bw.write(data); //刷新 bw.flush(); //写一行,换一行 bw.newLine(); } } 复制图片 //声明输入流,输出流对象 BufferedInputStream bis = null; BufferedOutputStream bos = null; try { //建立输入流和源文件之间的联系 bis = new BufferedInputStream(new FileInputStream(new File("E:/others/cat.png"))); //建立输出流和目标文之间的联系 bos = new BufferedOutputStream(new FileOutputStream(new File("D:/test/cat.png"))); //声明byte[]数组 byte[] buffer = new byte[1024]; //声明int变量 int len=0; while((len=bis.read(buffer))!=-1){ String s = new String(buffer,0,len); System.out.println("len="+len+",s="+s); bos.write(buffer, 0, len); bos.flush(); } **乱码原因** * 1.两边的编码方式不一致 * 2.保存的不完整,数据有丢失 * InputStreamReader,提供构造,可以传入编码方式 * InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader。 * ANSI ----->程序中gbk * * UTF-8----->程序中UTF-8 * * 乱码因为:ceshi.txt ANSI ,本地程序中UTF-8,不一致导致的。 * * 解决办法1:ceshi.txt 右击 另存为 UTF-8 * * 解决办法2:用InputStreamReader流 //声明输入流对象 InputStreamReader isr = null; try { //建立输入流和源文件之间的联系 isr = new InputStreamReader(new FileInputStream("E:/others/ceshi.txt"),"gbk"); //声明char[]数组,用于存储读取的内容 char[] buffer = new char[4]; //声明int变量,用来存储实际读取的字符数 int len = 0; while(-1 !=(len=isr.read(buffer))){ String s = new String(buffer,0,len); System.out.println(s); } ** FileInputStream与FileReader区别**: FileInputStream是字节流,FileReader是字符流,用字节流读取中文的时候,可能会出现乱码,而用字符流则不会出现乱码,而且用字符流读取的速度比字节流要快; **FileInputStream与BufferedInputStream区别** FileInputStream是字节流,BufferedInputStream是字节缓冲流,使用BufferedInputStream读资源比FileInputStream读取资源的效率高(BufferedInputStream的read方法会读取尽可能多的字节,执行read时先从缓冲区读取,当缓冲区数据读完时再把缓冲区填满。),因此,当每次读取的数据量很小时,FileInputStream每次都是从硬盘读入,而BufferedInputStream大部分是从缓冲区读入。读取内存速度比读取硬盘速度快得多,因此BufferedInputStream效率高,且FileInputStream对象的read方法会出现阻塞;BufferedInputStream的默认缓冲区大小是8192字节。当每次读取数据量接近或远超这个值时,两者效率就没有明显差别了。 **ObjectOutputStream/ObjectInputStream** java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。 java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。 **序列化** 只有实现了Serializable(si瑞尔奈zi包)和Externalizable接口的类的对象才能被序列化。 transient修饰属性避免序列化 序列化是指将对象转换成字节序列的过程称为对象的序列化,反序列化则是将字节序列恢复为对象的过程 对象的序列化通常有两种用途 1、把对象的字节序列永久的保存到硬盘上,通常存放到一个文件中 2、在网络上传送对象的序列化 *序列化步骤* 1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流; 2) 通过对象输出流的writeObject()方法写对象。 //声明输出流对象 ObjectOutputStream oos = null; try { //建立输出流和目标文件之间的联系 oos需要包装其他的底层流 oos = new ObjectOutputStream(new FileOutputStream(filePath)); //调用write()方法写出 List list.add(new Student("张伟",18)); list.add(new Student("张伟1",28)); list.add(new Student("张伟2",38)); //刷新 oos.writeObject(list); oos.flush(); } *反序列化步骤* 1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流; 2) 通过对象输入流的readObject()方法读取对象。 对象序列化和反序列范例: // 声明输入流对象 ObjectInputStream ois = null; try { // 建立输入流和源文件之间的联系 ,ois需要包装其他的底层流 ois = new ObjectInputStream(new FileInputStream(filepath)); // 通过read()方法读取 List for (Student temp : list) { System.out.println("取出的学生名:" + temp.getName() + ",年龄:" + temp.getAge() + ",性别:" + temp.getSex()); } } *serialVersionUID:* 字面意思上是序列化的版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量 private static final long serialVersionUID *显式地定义serialVersionUID有两种用途:* 1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID; 2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。 **RandomAccessFile类** RandomAccessFile类的主要功能是完成随机读取功能,可以读取指定位置的内容。 之前的File类只是针对文件本身进行操作的,而如果要想对文件内容进行操作,则可以使用RandomAccessFile类,此类属于随机读取类,可以随机读取一个文件中指定位置的数据。 * RandomAccessFile:既可以当输出流负责写出,也可以当输入流负责读取 * 体现它随机访问的特点 seek() skipBytes() * 先将3个用户信息,写入到E:/others/user.txt中 * 然后读取出来 * * 写入的时候,构造上 ,mode模式选用:rw,文件不存在,则创建 * * 读取的时候 mode:r 只读 * * 1.2个方法,一个方法负责写入 * 一个方法负责读取 * @author Administrator * */ public class TestRandomAccess01 { public static void main(String[] args) { String path = "E:/others/user.txt"; save(path); // readFirst(path); } /** * 顺序 ,1,2,3 * RandomAccessFile 当 输入流用 * @param filePath */ public static void readFirst(String filePath){ //声明输入流对象 RandomAccessFile ra= null; try { //建立输入流和源文件之间的联系 ra = new RandomAccessFile(new File(filePath), "r"); byte[] buffer = new byte[8]; for(int i=0;i buffer[i] = ra.readByte(); } //读取整数值 int age = ra.readInt(); System.out.println("第一个人信息:"+new String(buffer)+"---"+age); //读取第2个人 for(int i=0;i buffer[i] = ra.readByte(); } //读取整数值 age = ra.readInt(); System.out.println("第二个人信息:"+new String(buffer)+"---"+age); //读取第3个人 for(int i=0;i buffer[i] = ra.readByte(); } //读取整数值 age = ra.readInt(); System.out.println("第三个人信息:"+new String(buffer)+"---"+age); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(ra!=null){ try { ra.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 存储 * RandomAccessFile 当输出流用 * @param filePath */ public static void save(String filePath){ RandomAccessFile ra = null; try { //建立输出流和目标文件之间的联系,同时指定模式 ra = new RandomAccessFile(new File(filePath), "rw"); //调用write()方法写出 ra.writeBytes("zhangsan"); ra.writeInt(30); ra.writeBytes("lisi "); ra.writeInt(31); ra.writeBytes("wangwu "); ra.writeInt(32); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally{ if(ra!=null){ try { ra.close(); } catch (IOException e) { e.printStackTrace(); } } } } **线程** 进程:运行的程序,动态。是资源分配的基本单位。 一个进程可拥有多个并行的(concurrent)线程。 线程:进程中一段代码的执行过程。是执行和调度的基本单位。是进程中执行运算单位的最小单位,是进程内部的一个执行单元。 **实现多线程两种方式** *继承Thread类* 1.定义子类继承Thread类 2.子类重写Thread类run方法 3.创建Thread子类对象,即创建线程对象 4.调用线程对象的start()方法,启动线程 public class Rabbit extends Thread { private int step = 1; // (1)线程类中定义一个标志位 private boolean isRunning = true; public Rabbit() { } public Rabbit(String name) { super(name); // 调用父类Thread的带参构造 } @Override public void run() { // (2)线程体中使用该标志位 while (isRunning) { System.out.println(Thread.currentThread().getName() + "跑了" + (step++) + "步"); } } // 提供一个更改此标志位的方法 public void stopThread() { isRunning=false; } } public static void main(String[] args) { //新生状态 Rabbit ra = new Rabbit(); ra.setName("兔子"); //就绪状态 ra.start(); System.out.println("A判断兔子线程的是否处于活动状态:"+ra.isAlive()); //延迟2ms try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //终止线程 //ra.stop(); //可以用,但是不建议用,已过时的方法 //ra.destroy();//不可以用 ,不起作用 //(4)外部测试时候,调用更改此标志位的方法 ra.stopThread(); try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B判断兔子线程的是否处于活动状态:"+ra.isAlive()); } *实现Runnable接口* 1.定义子类实现Runnable接口 2.重写Runnable接口run()方法 3.通过Thread含构造器创建线程对象 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中 5.调用Thread类的start()方法 优点:1.避免单继承 2.方便共享资源,同一份资源,多个代理访问 **java中终止线程** *1.使用标志位* *2.中断策略* 使用标志位这种方法有个很大的局限性,那就是通过循环来使每次的操作都需要检查一下标志位。 java还提供了中断协作机制,能够使一个线程要求另外一个线程停止当前工作。其大致的思想为:调用线程Thread的interrupt([ˌɪntəˈrʌpt])方法,在线程内部通过捕获InterruptedException异常来决定线程是否继续还是退出。如下: class InterruptRunnable implements Runnable{ private BlockingQueue queue = new ArrayBlockingQueue(10); @Override public void run() { int i= 0; for (;;) { try { //线程的操作 i++; queue.put(i); } catch (InterruptedException e) { //捕获到了异常 该怎么做 System.out.println(queue); e.printStackTrace(); return; } } } 上述代码通过BlockingQueue的put方法来抛出InterruptedException异常。 当内部捕获到该异常时,从而决定是否继续还是直接退出了 public void testInterruptRunnable() throws InterruptedException { InterruptRunnable runnable = new InterruptRunnable(); Thread thread = new Thread(runnable); thread.start(); System.err.println(thread.isAlive()); Thread.sleep(1000); thread.interrupt(); Thread.sleep(1000); System.err.println(thread.isAlive()); Thread.sleep(1000); System.err.println(thread.isAlive()); } 该测试方法大致同使用标志位的测试方法,同样启动该线程后,1秒后调用线程的interrupt方法,从而触发Runnable的内部queue.put(i)操作抛出InterruptedException异常。 **进程的几种通信方式** 进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。 IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。 **多线程之线程间的通信方式** * wait/notify 等待 * Volatile 内存共享 **Yied放弃时间片(暂停线程)** * yield: 暂停当前正在执行的线程对象,并执行其他线程 * yield:加在哪个线程体里,就暂停谁 ,暂停不一定生效 class YieldDemo implements Runnable{ @Override public void run() { for(int i=0;i<=1000;i++){ /*if(i%20==0){ Thread.yield(); //暂停的线程A }*/ System.out.println(Thread.currentThread().getName()+"----"+i); } } } public class TestYield { public static void main(String[] args) { //创建真实角色类的实例 YieldDemo yd = new YieldDemo(); //创建代理角色,代理持有对真实角色的引用 Thread th = new Thread(yd,"线程A"); //通过代理开启 th.start(); //main()主线程中 for(int i=0;i<=1000;i++){ if(i%20==0){ Thread.yield(); //暂停的main(),线程A获得执行的机会 } System.out.println(Thread.currentThread().getName()+"----"+i); } } } **Join合并线程** * join:等待该线程终止。 * 阻塞线程,合并线程 * join()阻塞自身,让其他线程获得执行的机会,等其他线程执行完毕,自身才接着执行 。 * 加在哪个线程体里,就阻塞谁 class JoinDemo implements Runnable{ @Override public void run() { for(int i=0;i<=1000;i++){ System.out.println(Thread.currentThread().getName()+"----"+i); } } } public class TestJoin { public static void main(String[] args) { //创建真实角色 JoinDemo jd = new JoinDemo(); //创建代理,代理持有对真实角色的引用 Thread th = new Thread(jd,"线程a"); //通过代理启动 th.start(); for(int i=0;i<=1000;i++){ if(i==50){ try { th.join(); //阻塞main线程 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"----"+i); } } } **Synchronized线程的同步与锁** * Synchronized允许加在方法前,表示方法是线程安全的。 * 多个代理访问同一份资源,出现资源抢夺的问题,同步 :并发,多个线程访问同一份资源,确保资源安 全----线程安全 * 方式一:同步代码块 synchronized(引用类型、this/类.class){ ..... } 注意:使用实现Runnable接口方式创建多线程,同步代码块中的锁可以用this,如何使继承Thread类,慎this * 方式二:同步方法 访问修饰符 synchornized 返回值类型 方法名 (){...} **单例模式** 单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。 关键点: 1)一个类只有一个实例 这是最基本的 2)它必须自行创建这个实例 3)它必须自行向整个系统提供这个实例 两种实现方式: *懒汉模式* (类加载时不初始化) public class LazySingleton { //懒汉式单例模式 //比较懒,在类加载时,不创建实例,因此类加载速度快,但运行时获取对象的速度慢 private static LazySingleton intance = null;//静态私用成员,没有初始化 private LazySingleton() { //私有构造函数 } public static synchronized LazySingleton getInstance() //静态,同步,公开访问点 { if(intance == null) { intance = new LazySingleton(); } return intance; } } *饿汉模式* (在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快) public class EagerSingleton { //饿汉单例模式 //在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快 private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化 private EagerSingleton() { //私有构造函数 } public static EagerSingleton getInstance() //静态,不用同步(类加载时已初始化,不会有多线程的问题) { return instance; } } **wait()和notify()** * wait()方法:调用wait()方法,会挂起当前线程,并释放共享资源的锁. * notify()方法:调用了任意对象的notify()方法会在因调用该对象的wait()方法而阻塞的线程中随机选择一个解除阻塞,但要等到获得锁后才可真正执行。 * notifyAll()方法:调用了notifyAll()方法会将因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞。 * wait(),notify(),和notifyall()这3个方法都是Object类中的final方法,被所有的类继承且不允许重写。这3个方法只能在同步方法或同步代码块中使用,否则会抛出异常。 **wait()方法和sleep()方法 区别** * wait() :线程进入等待状态,不占用任何资源,不增加时间限制,因为wait方法会释放锁,所以调用该方法时,要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。 * sleep():线程睡眠,线程被调用时,占着cpu不工作,消耗内存资源,增加时间限制 ,必须捕获异常 **同步和异步的区别** 同步是指两个线程的运行是相关的,其中一个线程要阻塞等待另外一个线程的运行。异步的意思是两个线程毫无相关,自己运行自己的。 以通讯为例 同步:发送一个请求,等待返回,然后再发送下一个请求 异步:发送一个请求,不等待返回,随时可以再发送下一个请求 并发:同时发送多个请求 **socket编程** 套接字使用Tcp提供了两台计算机之间的通信机制 区分不同应用程序进程之间的网络通信和连接 ServerSocket用于服务器端,通过accept()监听请求,然后返回Socket。 Socket用于客户端 **TCP和UDP的区别** Tcp提供面向连接的,可靠的字节流传输,并且提供了拥塞控制和流量控制机制 UDP提供面向无连接的,不可靠的数据报的传输,不提供拥塞控制和流量控制机制 **Socket通信实现步骤:** 简化出Socket通信的实现步骤: 1.创建ServerSocket和Socket,建立连接 2.打开链接到Socket的输入/输出流 3.按照协议对Socket进行读/写操作 4.关闭输入输出流、关闭Socket *使用多线程实现多客户端的通信:* 多线程基本步骤: 1.服务器端创建ServerSocket,循环调用accept()等待客户端连接。 2.客户端创建一个socket并请求和服务器端连接。 3.服务器端接收客户端请求,创建socket与该客户建立专线连接。 4.建立连接的两个socket在一个单独的线程上对话。 5.服务器端继续等待新的连接 **NIO主要原理及使用** NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接、读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候,程序也可以做其他事情,以实现线程的异步操作。 考虑一个即时消息服务器,可能有上千个客户端同时连接到服务器,但是在任何时刻只有非常少量的消息需要读取和分发(如果采用线程池或者一线程一客户端方式,则会非常浪费资源),这就需要一种方法能阻塞等待,直到有一个信道可以进行I/O操作。NIO的Selector选择器就实现了这样的功能,一个Selector实例可以同时检查一组信道的I/O状态,它就类似一个观察者,只要我们把需要探知的SocketChannel告诉Selector,我们接着做别的事情,当有事件(比如,连接打开、数据到达等)发生时,它会通知我们,传回一组SelectionKey,我们读取这些Key,就会获得我们刚刚注册过的SocketChannel,然后,我们从这个Channel中读取数据,接着我们可以处理这些数据。 Selector内部原理实际是在做一个对所注册的Channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个Channel有所注册的事情发生,比如数据来了,它就会读取Channel中的数据,并对其进行处理。 要使用选择器,需要创建一个Selector实例,并将其注册到想要监控的信道上(通过Channel的方法实现)。最后调用选择器的select()方法,该方法会阻塞等待,直到有一个或多个信道准备好了I/O操作或等待超时,或另一个线程调用了该选择器的wakeup()方法。现在,在一个单独的线程中,通过调用select()方法,就能检查多个信道是否准备好进行I/O操作,由于非阻塞I/O的异步特性,在检查的同时,我们也可以执行其他任务。 基于NIO的TCP连接的建立步骤 服务端 1、传建一个Selector实例; 2、将其注册到各种信道,并指定每个信道上感兴趣的I/O操作; 3、重复执行: 1)调用一种select()方法; 2)获取选取的键列表; 3)对于已选键集中的每个键: a、获取信道,并从键中获取附件(如果为信道及其相关的key添加了附件的话); b、确定准备就绪的操纵并执行,如果是accept操作,将接收的信道设置为非阻塞模式,并注册到选择器; c、如果需要,修改键的兴趣操作集; d、从已选键集中移除键 客户端 与基于多线程的TCP客户端大致相同,只是这里是通过信道建立的连接,但在等待连接建立及读写时,我们可以异步地执行其他任务。 **double转byte类型** private static byte[] data; public static byte[] convert(double num) throws IOException { data = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeDouble(num); dos.flush(); data = bos.toByteArray(); dos.close(); return data; } **byte转double类型** public static double convert(byte[] data) throws IOException { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data)); double num = dis.readDouble(); dis.close(); return num; } **UDP通信** *发送引用数据类型* * 客户端: 1) 创建客户端 DatagramSocket类 +指定端口 2) 准备数据 字节数组 3) 打包 DatagramPacket +服务器地址及端口 4) 发送 5) 释放资源 * 服务器: 1) 创建服务端DatagramSocket类+指定端口 2) 准备接受容器 字节数组 封装DatagramPacket(封装成包) 3) 包 接收数据 4) 分析 5) 释放资源 *发送基本数据类型* * 客户端: 1) 创建客户端 DatagramSocket类 +指定端口 2) 准备数据 基本数据类型转换成字节数组(字节数组输出流ByteArrayOutputStream toByteArray 数据字节输出流DataOutputStream) 3) 打包 DatagramPacket +服务器地址及端口(发送的地点以及端口) 4) 发送 5) 释放资源 * 服务器: 1) 创建服务端DatagramSocket类+指定端口 2) 准备接受容器 字节数组 封装DatagramPacket(封装成包) 3) 包 接收数据 4) 分析数据 字节数组转换成基本数据类型(字节数组输入流ByteArrayOutputStream 数据字节输入流DataInputStream) 5) 释放资源