JAVASE学习笔记
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<arr.length-1;k++){
System.out.println("\n\n第"+(k+1)+"轮开始时:"+Arrays.toString(arr));
//内层控制的每一轮比较的次数,要保证比较次数在减少, 取值,4,3,2,1
for(int i=0;i<arr.length-1-k;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<arr.length-1;i++) {
int index=i;
for(int j=i+1;j<arr.length;j++) {
if(arr[j]<arr[index]) {
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<K,V>*
判断键值对数组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<K,V>[] segments;
ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示
tatic final class HashEntry<K,V> {
final K key;
final int hash;
volatile V value;
final HashEntry<K,V> next;
}
可以看到除了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<Student> list=new ArrayList<Student>();
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<Student> list = (List) ois.readObject();
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.length;i++){
buffer[i] = ra.readByte();
}
//读取整数值
int age = ra.readInt();
System.out.println("第一个人信息:"+new String(buffer)+"---"+age);
//读取第2个人
for(int i=0;i<buffer.length;i++){
buffer[i] = ra.readByte();
}
//读取整数值
age = ra.readInt();
System.out.println("第二个人信息:"+new String(buffer)+"---"+age);
//读取第3个人
for(int i=0;i<buffer.length;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) 释放资源