复习
1.ListIterator 是Iterator 的子接口,是专门用于遍历List 容器的迭代器,可以实现双向的遍历。
2.HashSet 元素无序,唯一,可以有一个null。底层使用HashMap 实现,只使用了HashMap的key 部分。Value是一个final static Object 对象。
3.TreeSet 元素有序(升序),唯一,底层使用TreeMap实现,只使用了TreeMap的key 部分。元素的有序是通过 内部比较器 java.lang.Comparable或者外部比较器java.util.Comparator 进行添加元素的时候的比较,优先使用 java.util.Comparator.
4.HashMap : 中保存的是键值对对象 Entry。底层使用哈希表实现。元素无序,唯一的。Key可以有一个null,value可以有多个null。Key要求是唯一的,无序的。整个Entry在哈希表中的位置key 的哈希码 决定。为了保证 key 的唯一性和无序性,那么key的对应的类型中需要重写hashCode 和 equals 方法。Key 的哈希码决定了整个Entry 在哈希表中的位置,equals保证了所有的Entry 的key 是唯一的。
5.HashMap 的遍历。得到所有的key通过 keySet();得到所有的value通过values()方法;得到所有的Entry通过entrySet()方法。
第一节 LinkedHashMap、TreeMap
1.LinkedHashMap
该类是HashMap的子类,HashMap的元素是无序(添加和遍历的顺序不一致)的,该类在原有的基础上增加了一个单独的链表,用来维护元素添加的顺序,遍历的顺序和添加的顺序一致了。遍历元素的效率和HashMap基本一致,但是修改元素的效率低了,因为要维护两个数据结构。
2.TreeMap
特点:底层使用二叉树实现,每一个节点中包含了key+value。节点的Key 是有序的(升序),唯一的,不能是null,value可以有多个null。value和对应的key是绑定的,key 的有序性是通过内部比较器或外部比较器实现的。根据内容效率介于HashMap 和 ArrayList之间。
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
/**
*TreeMap 的学习
*/
public class TestTreeMap {
public static void main(String[] args) {
asc();
deasc();
}
//升序
static void asc(){
Map<String,String> map=new TreeMap();
map.put("cn","China");
map.put("jp","Japan");
map.put("us","America");
map.put("uk","United Kingdom");
map.put("fr","France");
map.put("uk","England");
System.out.println(map);
}
//降序
static void deasc(){
Comparator<String> com=new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
};
Map<String,String> map=new TreeMap(com);
map.put("cn","China");
map.put("jp","Japan");
map.put("us","America");
map.put("uk","United Kingdom");
map.put("fr","France");
map.put("uk","England");
System.out.println(map);
}
}
第二节 其他的容器类、泛型
1.其他的容器类
1.Java.util.Hashtable 底层使用数据结构为哈希表,存储的是键值对 对象。功能基本和 HashMap一致。Hashtable 是早期的一个容器类,是一个线程安全的,效率相对较低的容器。HashMap 是线程非安全的,它的出现是为了在安全的情况下实现对Hashtable 的替代。Hashtable 的key 无序的,唯一的,但是key 和 value 都不能是null. Hashtable 早期的遍历的方式,得到所有的key的方法,和得到所有的value的方法,使用枚举器 进行遍历。
2.IdentifyHashMap :和 HashMap的唯一区别是,该容器中元素的 key ,只要key是一个区别于其他key 的key(比较的不是内容,而是地址),只要两个key 的地址不同,就认为是不同的key。
3.WeakHashMap :强引用、弱引用、虚引用。
4.Properties:是Hashtable 的子类,该类用于处理配置文件,该类没有泛型,key和value 必须都是String,对中文支持的不好。
/**
*HashTable和Property的学习
*/
public class Test {
public static void main(String[] args) {
test();
test1();
}
static void test(){
Hashtable hashtable=new Hashtable();
//早期获得所有的key的方法
Enumeration keys = hashtable.keys();
//早期的获得所有values的方法
Enumeration elements = hashtable.elements();
}
//jvm启动时会将系统中很多的信息加载到内存中。
//可以通过System获得。
static void test1(){
//得到系统信息的键值对 容器对象
Properties properties=System.getProperties();//是HashTable的子类
Enumeration<Object> keys = properties.keys();
while(keys.hasMoreElements()){
String key=keys.nextElement().toString();
String value=properties.getProperty(key);
System.out.println(key+"-->"+value);
}
}
2.Java.uitl.Collections
学习完数据学习了一个用于处理数组的工具类java.util.Arrays
Java.util.Collections 是一个专门用于处理容器对象的工具类。包含了一些工具方法。
3.泛型
Jdk1.5推出内容。在容器中使用泛型,可以避免加入不该加入的元素类型,避免获得元素之后进行类型的强转。
泛型又称为参数化类型。
泛型可以在方法、类、接口中定义使用。
1.泛型方法
/定义一个方法,可以接收任意类型为实参,并返回该实参的类型。
public static Object test(Object o){
return o;
}
/**
* 泛型方法的定义。
* <T> 是定义泛型方法的符号。T这个字符,只要是一个合法的标识符就行。
* 一般使用T 代表 任意的类型 Type E 表示元素的类型。
* @param t
* @param <T>
* @return
*/
public static <T> T test1(T t){
return t;
}
//方法中的泛型的上限下限的界定
//传入的实参 list 的元素的类型必须是 Number 类型或者是它的子类类型
//规定了泛型的上限
public static void test2(List<? extends Number> list){
}
//规定泛型的下限
// 容器的元素的泛型的类型必须是Number或者是Number 的父类类型
public static void test3(List<? super Number> list){
2.泛型类
//泛型类
class MyClass<T>{
T t;
T test(){
return null;
}
}
//设计类来描述栈
class MyStack<E>{
//定义栈的初始容量
public static final int DEFAULT_CAPACITY = 6;
//底层使用数组实现
private Object[] elementData;
//栈顶指针
private int index;
public MyStack(){
elementData = new Object[DEFAULT_CAPACITY];
}
//压栈操作
public void push(E o){
//栈满了。扩容,扩容的规则,1.5倍
if(isFull()){
//扩容
elementData = Arrays.copyOf(elementData,elementData.length * 3 >> 1);
}
elementData[index++] = o;
}
public E pop()throws Exception{
//栈空了
if(isEmpty()){
throw new Exception("感觉身体被掏空!");
}else{
//指针下移,然后将指针指向的位置的数据获得
// index --;
E o = (E)elementData[--index];
//避免内存泄露的
elementData[index] = null;
return o;
}
}
//获得栈顶元素,但是不移除。
public E peek()throws Exception{
//栈空了
if(isEmpty()){
throw new Exception("感觉身体被掏空!");
}else{
return (E)elementData[index-1];
}
}
/**
* 栈满了。
* @return 满了,返回true,否则false。
*/
private boolean isFull(){
return index == elementData.length;
}
public boolean isEmpty(){
return index == 0;
}
}
3.泛型接口
//泛型接口
interface MyInterface<T>{
T test();
}
//泛型接口的使用
//子类实现泛型接口的时候,指明泛型的具体类型。
class Student implements MyInterface<Student>{
@Override
public Student test() {
return null;
}
}
//子类实现泛型接口的时候,可以继续使用泛型。但是子类必须也使用泛型
class Stu<T> implements MyInterface<T>{
@Override
public T test() {
return null;
}
}
4. 泛型擦除
/**
* 泛型信息的擦除问题
* 对象的泛型的信息只在编译期有效,
* 泛型的信息安全检测是在编译期进行的,
* 到了运行期,所有的泛型的信息都将被擦除。
* 字节码中不包含泛型的相关的 信息。
* 以容器为例,运行期,元素的类型就不在做判断了。
*/
public class TestRemoveGen {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("abc");
// list.add(1);
//自动向上类型转换
Object o = list;
List l = (List)o;
l.add(1);
System.out.println(list);
List<Integer> list1 = new ArrayList<>();
//对象的类型信息的名字
System.out.println(list.getClass().getName());
System.out.println(list1.getClass().getName());
}
}
4.容器总结
1.哪些类可以使用迭代器遍历,实现了Iterable 接口的类都可以。List+Set
2.Collection 元素特点: 无序,不唯一,可以有多个null
3.List 继承了Collection 元素特点:有序,不唯一,可以有多个null。
4.ArrayList 是List 的子类,元素特点:有序,不唯一,可以有多个null。底层使用数组实现。是一个性能优良的容器类。
5.LinkedList 是List 的子类。元素特点 有序,不唯一,可以有多个null。底层使用双向链表实现。头部和尾部元素的添加和删除效率比较高。
6.ArrayList 和 LinkedList 的相同点和不同点。
7.ArrayList 和 Vector 的异同。
8.Set 元素特点:无序,唯一,单个null
9.HashSet 底层使用HashMap实现。
10.TreeSet底层使用TreeMap实现。
11.HashMap 底层使用哈希表实现,元素无序,key 是无序,唯一,单个null。Key的类型中需要重写hashCode 和equals。Value 无序,不唯一,多个null。
12.Object 类的hashCode 的默认实现。
13.TreeMap,底层使用二叉树实现,key 是有序的,升序,使用内部外部比较器进行排序比较。Key不能是null。
14.HashMap 和 Hashtable 的异同。
15.泛型方法、泛型类、泛型接口。
第三节 IO概述
1.IO 相关的概念
IO 指的是 Input 和 Output,输入输出。
概念:流 stream。是一个信息的通道,通过该通道可以实现对数据源的读写操作。
比较抽象的概念:对操作的数据源的抽象的表示形式。
可以通过IO流实现对文件的读写操作。
2.IO流的分类
1:根据数据的流向(参照程序内存)
输入流:将外部的数据源的数据可以读取到内存中的流。InputStream、Reader
输出流:将内存中的数据可以写到外部数据源的流。OutputStream、Writer
2:根据处理的数据单元
字节流:流处理数据的时候以字节为基本单位的流。InputStream、OutputStream
字符流:流处理数据的时候以字符为基本的单位的流。Reader、Writer
字符流只能对纯文本数据进行读写,字节流任何数据都可以,只不过字符流在处理字符数据上优势比较明显。
3:根据处理的数据的源头不同
节点流:直接以数据源为源头的流。FileInputStream,FileOutputStream。
处理流:以其他的流为数据源的流。BufferedInputStream BufferedOutputStream
3.IO的继承的体系
1.InputStream 是所有的字节输入流的父类,是一个抽象类。
2.OutputStream 是所有的字节输出流的父类,是一个抽象类。
3.Reader 是所有的字符输入流的父类,是一个抽象类。
4.Writer 是所有的字符输出流的父类,是一个抽象类。
第四节 文件字节字符流
1.文件字节输入输出流
FileInputStream:读取字节文件数据的。文件字节输入节点流。
FileOutputStream:对字节文件数据写入的。文件字节输出节点流。
import java.io.*;
/**
* FileInputStream 读取字节文件数据的,文件字节输入流
* FileOutputStream 对字节文件数据写入的,文件字节输出流
*/
public class TestFileStream {
public static void main(String[] args) throws IOException {
//创建一个文件
//new File("1.txt").createNewFile();
//readFile();
//readFile1();
readFile2();
//copyFile(new File("e:/1.png"),new File("e:/1_copy.png"));
}
//使用FileStream读取指定文件的数据
static void readFile(){
File file=new File("1.txt");
FileInputStream fis=null;
try {
//在程序和数据源之间搭建流对象,信息的通道
fis=new FileInputStream(file);
fis.skip(6);//跳过指定的字节数,从后继续读取
//通过流对象读取数据源的信息
//从数据源中读取下一个字节,并返回该字节
//如果数据源中没有返回数据返回-1;
int value=fis.read();
while(value!=-1){
System.out.print((char)value);
value=fis.read();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
//流对象在使用完毕之后必须关闭,不然GC回收不了
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//
static void readFile1(){
File file=new File("1.txt");
FileInputStream fis=null;
try {
fis=new FileInputStream(file);
byte[] buf=new byte[10];
int count=0;
while((count=fis.read(buf))!=-1){
String str=new String(buf,0,count);
System.out.println(str);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
//去乱码的
static void readFile2(){
File file=new File("1.txt");
FileInputStream fis=null;
try {
//得到文件的字节数
//long length=file.length();
//查看流对象中可读的字节数
// fis.available();
long length=fis.available();
fis=new FileInputStream(file);
//创建与文件大小相同的字节数组
byte[] buf=new byte[(int)length];//注意数组的长度是int类型这里要将long类型强制转化为int类型
fis.read(buf);
String str=new String(buf);
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
//流对象使用完毕之后,不许关闭不然GC回收不了
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//将指定的字符串写到指定的文件中
public static void writeFile(){
FileOutputStream fos=null;
try {
fos=new FileOutputStream("2.txt");
//将字符写入文件
fos.write(65);//A
fos.write('\n');//
fos.write(97);//a
fos.write('\n');
String str="千万里我追寻着你";
//编码的过程
byte[] bytes=str.getBytes();
fos.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
//使用FileInputStream和FileOutputStream实现文件的复制
public static void copyFile(File srcFile,File destFile){
FileInputStream fis=null;
FileOutputStream fos=null;
try {
fis=new FileInputStream(srcFile);
fos=new FileOutputStream(destFile);
byte[] buf=new byte[1024];
int count=fis.read(buf);
while(count!=-1){
fos.write(buf,0,count);
count=fis.read(buf);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//关于FileOutputStream 的问题
static void test1() throws Exception{
//使用下面的构造方法创建的fos对象,当代码一执行
//会将数据源清空。然后再讲写入的内容写入。
//如果想实现文件数据的尾部添加,那么就添加第二个参数。true代表 尾部追加
//false 代表先清空再追加。
FileOutputStream fos=new FileOutputStream("2.txt",true);
fos.write("你好么".getBytes());//追加写入
fos.close();
}
}
2.文件字符输入输出流
/**
* FileReader:从文本数据读取字符
* FileWrite:向文本写入字符
*/
public class TestFileReadWrite {
public static void main(String[] args) {
copyFile("e:/a.txt","e:/a_copy.txt");
}
//从指定的文件中读取字符数据
static void readFile(File file){
FileReader fr=null;
try {
fr=new FileReader(file);
//读取一个字符数据带到末尾返回-1
int value=fr.read();
while(value!=-1){
System.out.println((char)value);
value=fr.read();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(fr!=null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//文本复制
static void copyFile(String srcPath,String destPath){
FileReader fr=null;
FileWriter fw=null;
try {
fr=new FileReader(srcPath);
fw=new FileWriter(destPath);
char[] buf=new char[10];
//从数据源一次读取多个字符数据,放到buf中,并返回本次读取到的有效的字符个数
int count=fr.read(buf);
while(count!=-1){
fw.write(new String(buf,0,count));
count=fr.read(buf);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
//文本复制
static void copyFile1(String srcPath,String destPath) throws Exception{
FileReader fr=new FileReader(srcPath);
FileWriter fw=new FileWriter(destPath);
char[] buf=new char[10];
//从数据源一次读取多个字符数据,放到buf中,并返回本次读取到的有效字符个数
int count=fr.read(buf);
while(count!=-1){
fw.write(new String(buf,0,count));
count=fr.read(buf);
}
fr.close();
fw.close();
}
}
3.带缓冲区的字节输入输出流
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 带缓存区的字节出入输出流
*/
public class TestBufferStream{
public static void main(String[] args) {
}
//
static void copyFile(String srcPath,String destPath) throws Exception{
//节点流
FileInputStream fis=new FileInputStream(srcPath);
BufferedInputStream bis=new BufferedInputStream(fis);
FileOutputStream fos=new FileOutputStream(destPath);
BufferedOutputStream bos=new BufferedOutputStream(fos);
byte[] buf=new byte[10];
int count=bis.read(buf);
while(count!=-1){
bos.write(buf,0,count);
count=bis.read(buf);
}
//处理流的关闭问题,之关闭处理流即可
bis.close();
bos.close();
//如果节点流和处理流都关闭,那么先打开的后关闭
}
}
今日练习:
1:接收键盘输入,将输入的内容写入到指定的文件,当输入bye 的时候,结束输入。
(转) :Java 编程下 IO 中的输入流的 read() 方法返回值为什么是 int 值
Java 下 IO 中 FileReder 和 FileInputStream 分别是以字符和字节的形式来完成数据的读取的,然而返回值确是 int 类型的数据,这样做的核心目的只是要取到到一个 int 类型下的 -1 来表示数据流的末尾。为什么要这样做?又是怎么实现的呢?
首先看 FileReder :
FileReader fr = new FileReader(“src.txt”);
int ch = fr.read();
如上面的代码,FileReader 的 read 方法返回值是一个 int 类型的变量来接收的,然而 read 方法在实际中却是以字符形式来进行数据的读取的。通过上面的基本数据类型的取值范围我们能发现 char 类型数据的取值范围为 0 ~ 65535 ,也就是说 char 类型数据是取不到负值的;int 类型数据的取值范围为 -2147483648 ~ 2147483647 ,可以取到负值;同时 int 的取值范围又包含 char 的取值范围,这就为使用 int 作为返回值类型提供了可能,因为流需要一个特殊的值来表示流末尾,这个值不应该在 char 的取值范围内,如果使用 char 取值范围内的值作为流末尾标志,那么这个值同样有可能出现在数据流中间作为数据来传输,流在读到这个值的时候会认为已经到达流末尾,后面未读取的数据将被截断。所以 Java 中选择了使用 -1 来作为流末尾,这个值不在 char 的取值范围内,所以不存在数据截断,然而 -1 又在 int 的取值范围内,同时 int 的取值范围包含 char 的取值范围,所以 FileReader 下 read 方法返回的 char 类型数据直接转为了 int 类型。
再看 FileInputStream :
FileInputStream fis = new FileInputStream(“src.txt”);
int b = fis.read();
同理 FileInputStream 也需要一个自己取不到的值来作为流末尾的标志,Java 同样使用 -1 来作为字节流的流末尾,从上面基本数据类型的取值范围我们可以看到 byte 的取值范围为 -128 ~ 127 ,这就意味走着 byte 可以取到 -1 ,如果把 -1 直接当作 int 作为流末尾,那么就无法区分这个读到的结果是流末尾还是流中的数据了,那么 Java 是如何实现取值 -1 的呢?在 Java 内部,Java 通过高位补 0 来实现数据从 byte 到 int 的转换,举个例子:
-1 在 byte 类型和 int 类型中都可以取到,-1 在 byte 类型下的二进制存储形式为 11111111 ,然而使用 read 方法的时候,Java 内部将 byte 的高位补 0 将 byte 转为 int 类型,所以 byte 类型的 -1 在 int 类型下的二进制存储形式为 00000000 00000000 00000000 11111111,对应的 int 值为 255,通过高位补 0 ,所有 byte 类型的负数都转为了正数。然而在使用这些读到的 byte 数据时,只要将这些数据从 int 强转回 byte 即可得到原有的数据。所以就可以使用 -1 来作为流末尾的标志,因为 Java 内部将 byte 的负数通过高位补 0 将其转换为了负数。